This model was last run on 2020-04-01 16:11:40

Contents

Background and Scope

Feature Selection

Clustering - Kmeans

Bootstrapping - Kmeans

Final Outputs

Appendix

Clustering - Hierarchical Clustering

Bootstrapping - Hierarchical Clustering

Background and Scope

Different teams at Deliveroo have come up with different zone segmentation methods in the past. However, all of these have been made with a specific agenda. For example, Project Hopper has segmented zones based on the costs required to improve their selection-service trade off, while the Pricing team has segmented zones based on price-related factors.

The aim here is to come up with a non-strategic set of zone clusters, which clusters zones according to their fundamental features.

The scope of this analysis is to segment zones in RUK, before automating the segmentation method across different countries. All features were computed using data between 1st January and 29th February 2020. This was to avoid abnormal behaviour due to the Coronavirus outbreak. Competitor features were computed using data between 1st and 7th March 2020, due to unreliable Ubereats scrapes before March.




#df$comp_uber_rx_supply[is.na(df$comp_uber_rx_supply)] <- 0
df$pct_mplus <- df$rx_mplus/df$live_rx_total
df$aof <- df$aof/2 #silly sql mistake sorry

df$zone_tenure_days[df$zone_code == 'LVN'] <-  as.numeric(as.Date('2020-03-14') - as.Date('2020-01-09'))

#df$comp_number_competitors <- df$comp_justeat_bool + df$comp_uber_bool


factors_demo <- c('zone_tenure_days', 'zone_population', 'area', 'popn_density', 'zones_in_city', 'pct_zone_of_city')
factors_rx_supply <- c('live_rx_total', 'rx_per_thousand_popn', 'has_editions_site', "rx_core", "rx_editions", "rx_mplus", "pct_mplus", 'avg_price_category', 'number_cuisines')
factors_rx_cuisine <- names(df)[grepl("num_.+_rx", names(df))]
factors_rx_type <- names(df)[grepl("num_rx.+", names(df))]
factors_comp <-  names(df)[grepl("comp.+", names(df))]
factors_cust <- c("orders", "customers", "customers_new", "customers_existing", "aof", "aov", "avg_credits_vouchers", "plus_orders_pct", "convenience_orders_pct")
factors_riders <- c("cnt_riders_3w", "scooter_deliv_pct", "bicycle_deliv_pct", "car_deliv_pct")

live_rx_cc <- rowSums(df[,factors_rx_cuisine])
for(i in factors_rx_cuisine) {
  df[[paste0(i,"_pct")]] <- df[[i]]/ live_rx_cc
}

live_rx_type <- rowSums(df[,factors_rx_type]) 
for(i in factors_rx_type) {
  df[[paste0(i,"_pct")]] <- df[[i]]/ live_rx_type
}

df$num_rx_enterprise_pct <- df$num_rx_entgold_pct + df$num_rx_entsilv_pct
df$num_rx_local_pct <- df$num_rx_logold_pct + df$num_rx_losilv_pct + df$num_rx_lobron_pct

factors_rx_cuisine <- names(df)[grepl("num_.+_rx_pct", names(df))]
factors_rx_type <- names(df)[grepl("num_rx_.+_pct", names(df))]

Feature Selection

We group features into the following segments:

  1. Demographic features: Zone area, population density, number of zones in city, etc
  2. Restaurant supply features: Number of live restaurants, number of core restaurants, zone average price category, etc.
  3. Restaurant cuisine features: Number of restaurants available for each Deliveroo cuisine category
  4. Competitor features: Booleans for which competitors are active in the zone, total restaurant supply per competitor
  5. Customer features: New customers, existing customers, AOF, AOV, percentage of plus orders, etc.
  6. Rider features: Number of total riders, percentage split by vehicle, etc.

We do not use features like EOD and number of B10s, as these are levers that can be easily pulled by Deliveroo, rather than underlying features of a zone. We ultimately narrowed these features down into 12 variables that we used, to leep the model parsimonious.

We normalize each variable, and use correlation matrices to remove features that are highly correlated to each other. Below is the correlation matrix for restaurant-related factors.

df_vars <- df[,c(factors_demo, factors_rx_supply)]


normalize <- function(x) {
return ((x - min(x)) / (max(x) - min(x)))                        
}


cols_to_normalize <- names(df_vars)[1:ncol(df_vars)]

for (i in cols_to_normalize) {
  df_vars[[paste0("norm_", i)]] <- normalize(df_vars[[i]])
}

df_vars2 <- df_vars[,-which(names(df_vars) %in% cols_to_normalize)]
names(df_vars2) <- gsub("norm_", "", names(df_vars2))


c <- cor(df_vars2[,1:ncol(df_vars2)])

corrplot(c, method = "number", type="upper", order="original",
         col=brewer.pal(n=8, name="RdYlBu"), tl.cex =0.8, number.cex = 0.6)

NA
NA
NA

Here is a correlation matrix with the main demographic and customer features.

We thus remove features with a correlation score above 0.7. These are: percentage population of zone vs city , total core restaurants, number of mplus restaurants, existing customers, etc. For restaurant type, we only keep features for G6, combined enterprise (Gold, Silver) and combined local restaurants.

Just a note, for variables around rider vehicle type, some zones have all vehicles classified as NULL. These are predominantly Marketplace-only zones.


df_vars <- df[,c("zone_code", factors_demo, factors_rx_supply, "num_rx_g25_pct", "num_rx_enterprise_pct", factors_comp, factors_riders)]

cols_to_remove <- c('pct_zone_of_city', 'rx_core', 'orders', 'customers', 'customers_existing', 'rx_mplus', 'rx_editions', 'num_african_rx_pct', 'num_malay_rx_pct', 'cnt_riders_3w', 'rx_per_thousand_popn', 'avg_price_category', 'scooter_deliv_pct', 'bicycle_deliv_pct', "num_rx_local_pct", "has_editions_site", 'comp_justeat_bool',
                    'comp_uber_bool', "area")
df_vars <- df_vars[,-which(names(df_vars) %in% cols_to_remove)]
        
cols_to_normalize <- names(df_vars)[2:ncol(df_vars)]

for (i in cols_to_normalize) {
  df_vars[[paste0("norm_", i)]] <- normalize(df_vars[[i]])
}

df_vars2 <- df_vars[,-which(names(df_vars) %in% cols_to_normalize)]
names(df_vars2) <- gsub("norm_", "", names(df_vars2))
#df_vars2 <- subset(df_vars2, select = - c(num_african_rx_pct, num_malay_rx_pct))

We do a quick Principal Component Analysis (PCA) to understand which features are correlated, and also see what differentiates different zones from each other. The first 2 principal components explain 0.60309 of the total variation, so do keep in mind that there are other orthogonal principal components we have not visualised.

Below is a biplot based on our dataset.


set.seed(1234)
#df_vars2 <- df_vars2[1:10,]
df.pca <- prcomp(df_vars2[,2:ncol(df_vars2)], center = T, scale = T)
comp <- cbind(zone_code = df_vars2$zone_code, data.frame(df.pca$x))


rownames(df_vars2) <- df_vars2$zone_code
#summary(df.pca)

#biplot(df.pca)

# autoplot(df.pca, shape = F,
#         data = df_vars2, label = T, label.size = 2,
#         loadings = T, loadings.label = T, loadings.colour = 'blue', loadings.label.size = 4,
#         loadings.label.vjust = 0, loadings.label.hjust = 0,
#          xlim = c(-0.06,0.165), ylim = c(-0.15,0.175), position = "jitter")

autoplot(df.pca, data = df_vars2, label = T, shape = F, label.size = 2.5,
         loadings = T, loadings.label = T, loadings.colour = 'blue', loadings.label.size = 4,
loadings.label.vjust = -1, loadings.label.hjust = 1)

NA
NA

Factors that are most important for differentiating zones are number of live restaurants in the zone, number of cuisines, how long the zone has been open (zone_tenure), the percaentage of M+ restaurants, JustEat and Uber’s restaurant supply, etc.

Some factors are significantly correlated to zone size and density, such as UberEat’s restaurant supply.

The biplot allows us to see what makes 2 zones similar or dissimilar. For example, MC (Manchester Central) and BNC (Brighton Central) are pretty similar along features that affect PC1 - high zone tenure, high number of cuisines, high restaurant supply, both have Editions sites, etc. What differentiates them is the number of zones in their respective city/metropolitan areas (26 vs. 1 zone), percentage of car deliveries in the zone (1% vs. 11%), their competitive landscape (Justeat and Ubereats have much higher supply in MC, whereas Deliveroo supply is roughly equal in both zones), etc.



Clustering using Kmeans

We now cluster this data using kmeans. A scree plot shows that the optimal number of clusters is around 5 or 6, as beyond that, the reduction in the total within-cluster sum of squares tapers off.

k.max <- 15

wss <- sapply(1:k.max,
              function(k){kmeans(df_vars2[,2:ncol(df_vars2)], k, nstart=50,iter.max = 15 )$tot.withinss})
plot(1:k.max, wss,
     type="b", pch = 19, frame = FALSE,
     xlab="Number of clusters K",
     ylab="Total within-clusters sum of squares")

We then segment the zones into 5 clusters, seen below.

seed <- sample(c(1:1000),1)
set.seed(926)


k <- 5
rownames(df_vars2) <- df_vars2$zone_code
op.kmeans <- kmeans(df_vars2[,2:ncol(df_vars2)], k)


clusters <- data.frame(zone_code = rownames(as.data.frame(op.kmeans$cluster)),
                       cluster = op.kmeans$cluster)


df_final <- merge(clusters, df,
                  by = 'zone_code',
                  all.x = T)


autoplot(op.kmeans, data = df_vars2, label = TRUE, label.size = 3
         , shape = F
         , frame = T)


#print(seed)

The clusters represent 5 groups of zones: Urban Winners, Outer City, Momentum - New Launch and Relaunched, Opportunity - Wealthy and University Towns, and Why are we here? - M+ Zones. Below is a summary of their main characteristics.


clus1 <- c('Nickname: Urban Winners'
, 'Very large zone population and very high population density'
, 'Zone is >4 years old'
, 'Mix of large and smaller metropolitan areas - mainly city centres'
, 'Highest rx supply, very high cuisine diversity, not many M+ restaurants'
, 'Deliveroo dominates the competition'
, 'Only cluster with Editions sites, not many M+ restaurants'
, 'Only cluster where most deliveries are on bikes/scooters'
, 'Example zones: Brighton Central, Birmingham, Cambridge, Liverpool City Centre, Edinburgh North/South, Portsmouth'
, '')


clus2 <- c('Nickname: Opportunity'
, 'Medium zone population'
, 'Most zones are ~ 3 years old'
, 'Small metropolitan areas, most zones are the only zone in their city'
, 'Decent rx supply, low number of M+ restaurants'
, 'Good proportion of G6 and Enterprise restaurants'
, 'Relatively high density of restaurants, good cuisine diversity'
, 'Justeat and Deliveroo competing, Ubereats present but has lower rx supply'
, 'Example zones:  Aberdeen, Cheltenham, Slough, Lancaster, Exeter, Warwick, Durham'
, '')


clus5 <- c('Nickname: Outer City'
, 'Large zone population'
, 'Zone is 2 - 3 years old'
, 'Very large metropolitan areas - most zones are outer Birmingham and Manchester'
, 'Decent rx supply, decent cuisine diversity'
, 'Justeat dominates in terms of restaurant supply'
, '37% (second highest) M+ restaurants'
, 'Example zones: Walsall,  Bolton, Wolverhampton, Dudley, Edgbaston'
, ''
, '')


clus4 <- c('Nickname: Momentum - New Launch and Relaunched'
, 'Medium zone population and low population density'
, 'Most zones < 1 year old, some older zones'
, 'Mainly new launch or relaunched (eg Livingston) zones, handful of older zones (Bangor, Beeston, Bedford)'
, 'Low rx supply, low cuisine diversity'
, 'Justeat doing better than Deliveroo and Uber - Uber not in 25% of zones.'
, 'Highest proportion of G6 restaurants, very high M+ restaurants'
, 'Mainly car deliveries'
, 'Example zones:  Doncaster, Scunthorpe, Weymouth, Sunderland, Burton upon Trent, Derry City Centre'
, '')


clus3 <- c('Nickname: Why are we here? and M+ only zones'
, 'Range of zone populations, and medium population density'
, 'Range of small to large metropolitan areas'
, 'Most zones are < 2 years old'
, 'Lowest rx supply, lowest cuisine diversity'
, '84% M+ restaurants'
, 'Justeat dominates, Uber not present in 20% of the zones'
, 'Main difference between this cluster and << is M+ restaurants, population density, and justeat rx supply'
, 'Example zones: Outer Newcastle, Outer Rotherham, Outer Sheffield, Manchester East, Seaham'
, '')



clus_desc <- data.frame(Cluster1 = clus1,
                   Cluster2 = clus2,
                   Cluster3 = clus3,
                   Cluster4 = clus4,
                   Cluster5 = clus5)

clus_desc
NA

Here is a more detailed view of the features in each zone. Take a look at the biplot above to understand which features are more important in differentiating them. The most important features are number of live restaurants, number of cuisines, zone tenure, competitive supply and percentage of M+ restaurants. We’ve also summarised some features that were not used in the clustering, just to illustrate the difference in the clusters more clearly.


df_summ <- df_final %>%
  group_by(cluster) %>%
  summarise(number_of_zones = n(),
            zone_tenure_days = round(mean(zone_tenure_days)),
            zone_population = round(mean(zone_population)),
            #area = mean(area),
            popn_density = round(mean(popn_density)),
            zones_in_city = round(mean(zones_in_city)),
            live_rx_total = round(mean(live_rx_total)),
            number_cuisines = mean(number_cuisines),
            #rx_per_thousand_popn = mean(rx_per_thousand_popn),
            has_editions_site = mean(has_editions_site),
            pct_mplus = mean(pct_mplus),
            avg_price_category = mean(avg_price_category),
            car_deliv_pct = mean(car_deliv_pct),

            comp_justeat_bool = mean(comp_justeat_bool),
            comp_uber_bool = mean(comp_uber_bool),
            comp_justeat_rx_supply = round(mean(comp_justeat_rx_supply)),
            comp_uber_rx_supply = round(mean(comp_uber_rx_supply)),
            # num_rx_g6 = mean(num_rx_g6),
            # num_rx_enterprise = mean(num_rx_enterprise),
            # num_rx_local = mean(num_rx_local),
            num_rx_g25_pct = mean(num_rx_g25_pct),
            num_rx_enterprise_pct = mean(num_rx_enterprise_pct),
            #num_rx_local_pct = mean(num_rx_local_pct),
            #num_rx_nosegment_pct = mean(num_rx_nosegment_pct),
            # aof = mean(aof),
            # aov = mean(aov),
            #plus_orders_pct = mean(plus_orders_pct),
            # convenience_orders_pct = mean(convenience_orders_pct),
    
            # bicycle_deliv_pct = mean (bicycle_deliv_pct),
            # scooter_deliv_pct = mean(scooter_deliv_pct),
            # null_deliv_pct = mean(null_deliv_pct),
            # num_alcohol_rx_pct   = mean(num_alcohol_rx_pct),
            # num_american_rx_pct = mean(num_american_rx_pct),
            # num_british_rx_pct = mean(num_british_rx_pct ),
            # num_chinese_rx_pct = mean(num_chinese_rx_pct), 
            # num_dessert_rx_pct = mean(num_dessert_rx_pct),
            # num_french_rx_pct = mean(num_french_rx_pct),
            # num_greek_rx_pct = mean(num_greek_rx_pct),
            # num_indian_rx_pct  = mean(num_indian_rx_pct ),
            # num_italian_rx_pct = mean(num_italian_rx_pct),
            # num_japanese_rx_pct = mean(num_japanese_rx_pct), 
            # num_korean_rx_pct  = mean(num_korean_rx_pct ),
            # num_lebanese_rx_pct  = mean(num_lebanese_rx_pct), 
            # num_mexican_rx_pct = mean(num_mexican_rx_pct ),
            # num_spanish_rx_pct = mean(num_spanish_rx_pct),
            # num_thai_rx_pct = mean(num_thai_rx_pct),
            # num_turkish_rx_pct = mean(num_turkish_rx_pct),
            # num_vietnamese_rx_pct = mean(num_vietnamese_rx_pct),
            # num_missing_rx_pct = mean(num_missing_rx_pct)
            )
  
nums <- vapply(df_summ, is.numeric, FUN.VALUE = logical(1))
df_summ[,nums] <- round(df_summ[,nums], digits = 2)

df_compare <- as.data.frame(t(df_summ))
names(df_compare) <- c('Cluster 1', 'Cluster 2', 'Cluster 3', 'Cluster 4', 'Cluster 5')
df_compare <- df_compare[-1,]

df_compare
NA
# 
# cluster_df <- do.call(cbind, cluster_list)
# 
# cluster_df <- cluster_df[,c(1,2,6)]
# names(cluster_df)[1] <- "zone_code"
# cluster_df$zone_change <- with(cluster_df, ifelse(cust_5.cluster == nocust_5.cluster, "N", "Y"))
# 
# cluster_df_Y <- cluster_df[cluster_df$zone_change == 'Y',]
# 
# 
# change1 <- cluster_df_Y$zone_code[cluster_df_Y$cust_5.cluster == 3 & cluster_df_Y$nocust_5.cluster == 4]
# change2 <- cluster_df_Y$zone_code[cluster_df_Y$cust_5.cluster == 4 & cluster_df_Y$nocust_5.cluster == 3]
# change3 <- cluster_df_Y$zone_code[cluster_df_Y$cust_5.cluster == 4 & cluster_df_Y$nocust_5.cluster == 1]
# change4 <- cluster_df_Y$zone_code[cluster_df_Y$cust_5.cluster == 3 & cluster_df_Y$nocust_5.cluster == 5]

There was some debate around whether customer-related factors like AOF and number of new customers should be included in the clustering algorithm. We removed customer-related factors to see how the clusters changed. Only 7.69% of clusters changed.



Bootstrapping Kmeans

We need to understand how well our clustering algorithm has performed - do our 5 clusters represent actual structure in the data, or are they products of our algorithm? Kmeans is an unsupervised machine learning algorithm, so there is no “right” answer to compare our outcomes to. Furthermore, the clusters specified can vary depending on where the initial centroid (centre of the cluster) is set.

We check whether our clusters represent a true structure by seeing if they remain stable under plausible variations in the dataset. We check the stability of our clusters the following way:

  1. Bootstrap a sample of our feature dataset
    • Sample with replacement

  2. Implement the k-means algorithm on the bootstrapped data

  3. Compare the similarity of the clusters in (2) to our initial clustering output
    • Look at pairwise combinations of zones, and compute how many remain in the same cluster

  4. Iterate over steps 1 - 3, 500 times to see the cumulative proportion of zone pairs that dissolve over 500 iterations

For example, imagine that our original clustering results in a cluster:
\(A = \{EDN, CAM, CHE, TON\}\)

We obtain 10 possible pairwise combinations: \(EDN-EDN, EDN-CAM, EDN-CHE, EDN-TON, CAM-CAM\) \(CAM-CHE, CAM-TON, CHE-CHE, CHE-TON, TON-TON\)
Note that we account for repeats of the same zone, and these combinations might occure in the bootstrapped data.

Our first bootstrapped implementation throws up a cluster:
\(B =\{CAM, CAM, CHE, WCV\}\)

Note that some repetition occurs due to us sampling with replacement (CAM), which also causes EDN and TON to not be picked in the random sample. This cluster also has a new zone, WCV, which was not assigned to that cluster originally.
We obtain 4 possible pairwise combinations: \(CAM-CAM, CAM-CHE, CAM-WCV, CHE-WCV\). 2 of these 4 pairs occur in our original cluster, so we calculate our similarity index as \(Similarity = 2/4 = 0.5\)

We initially used the Jaccard index to evaluate the stability of our clusters, as that is the most popular method. However, we ultimately realised that this was not applicable to our business problem.

In the example above, we would calculate the Jaccard index as \(J(A,B) = \{A ∩ B\}/\{A U B\} = \{CAM,CHE\} / \{EDN, CAM, CHE, TON, WCV\} = 0.4\)

This would result in a low Jaccard Index, simply because zones EDN and TON were not sampled in the bootstrapped data. Sampling with replacement meant that our bootstrapped clusters would almost always have less zones than our original clusters. So, the Jaccard index would unfairly penalize us for that.

start <- Sys.time()

cluster_list <- list()
cluster_df_list <- list()
pairwise_list_boot <- list()


sample_size <- nrow(df_vars2)
n_iter <- 1:200
n_clust <- 1:k
  
for (i in n_iter) {

  rows_to_sample <- sample(c(1:sample_size), replace = T)
  df_boot <- df_vars2[rows_to_sample,]
  
  #df_boot <- df_vars2
  
      if(i == 1) {
        op.kmeans.boot  <- op.kmeans #original clusters
      } else {
      set.seed(sample(c(1:1000),1))
      op.kmeans.boot <- kmeans(df_boot[,2:ncol(df_boot)], k, iter.max = 10)
      }
  
  
  zone_code_names <- rownames(as.data.frame(op.kmeans.boot$cluster))
  zone_code_names <- sub(".[1-9]+", "", zone_code_names) #to prevent re-naming of repeated zones
  
  clusters <- data.frame(zone_code = zone_code_names,
                         cluster = op.kmeans.boot$cluster,
                         iteration = i)
  clusters$zone_code <- as.character(clusters$zone_code)
  
  pairwise_list_boot.sub <- list()
  
  for (m in n_clust) {
    sub.clust <- clusters$zone_code[clusters$cluster == m]
    for (n in 1:length(sub.clust)) {
      for (p in 1:length(sub.clust)) {
      pair <- paste(sort(c(sub.clust[n], sub.clust[p])), collapse = "-")
      pairwise_list_boot.sub[[pair]] <- pair
    }
    }

  }

      # for (m in 1:nrow(clusters)) {
      #   for( n in 1:nrow(clusters)) {
      #     if (clusters[m,2] == clusters[n,2]) {
      #       pair <- paste(sort(c(clusters[m,1], clusters[n,1])), collapse = "-")
      #       pairwise_list_boot.sub[[pair]] <- pair
      #     }
      #   }
      # }
  
  pairwise_list_boot[[length(pairwise_list_boot)+ 1]] <- pairwise_list_boot.sub
  
  for (n in n_clust) {
    cluster_list[[paste0(i, "-", n)]] <- as.vector(clusters$zone_code[clusters$cluster == n])
  }
  cluster_df_list[[i]] <- clusters

  paste0("Completed: Iteration ",i)
}

end <- Sys.time()
  
time.taken <- end - start  
  

df_final_boot <- do.call(rbind, cluster_df_list)

numer_list <- list()
denom_list <- list()

for (i in 2:length(pairwise_list_boot)) {
  orig <- pairwise_list_boot[[1]]
  test <- pairwise_list_boot[[i]]
  numer <- sum(test %in% orig)
  denom <- length(test)
  numer_list [[length(numer_list )+1]] <- numer
  denom_list [[length(denom_list )+1]] <- denom
  }

numer_df <- t(data.frame(numer_list))
denom_df <- t(data.frame(denom_list))
iter_df <- data.frame(t = c(1:length(numer_df)),
                      orig_pairs = numer_df,
                      total_pairs = denom_df)

iter_df$cumu_orig_pairs <- cumsum(iter_df$orig_pairs)
iter_df$cumu_total_pairs <- cumsum(iter_df$total_pairs)
iter_df$mismatch <- with(iter_df, 1- (cumu_orig_pairs/cumu_total_pairs))

iter_df_kmeans <- iter_df

We see that the stability of our clusters is quite high. Over time, around 13.08% of our zone-pairs end up segmented into different clusters. This means that 86.92% of our zone-pairs remain in the same cluster. This is great.

plot_ly( x = iter_df_kmeans$t, y = iter_df_kmeans$mismatch, mode = 'lines', type = 'scatter') %>%
  layout(title = "% of incorrect pairwise matches over time",
         xaxis = list(title = "Number of Iterations"), 
         yaxis = list(title = "% Incorrect Matches", range = c(0,0.4)))


# plot_ly( y = iter_comp$pct_dissolved, mode = 'lines', type = 'scatter') %>%
#   layout(title = "% of dissolved clusters over time",
#          xaxis = list(title = "Number of Iterations"),
#          yaxis = list(title = "% Dissolved Clusters"))

Final Outputs


Appendix


Clustering using Hierarchical Clustering

We ultimately decided against hierarchical clustering for 2 reasons:

    1. Hierarchical clustering does not work well with large datasets
    1. The final cluster outputs were not as intuitive as the kmeans clusters


We also use hierarchical clustering to segment our zones, to see how the results compare to to Kmeans. We use the same set of features used for Kmeans clustering, and start clustering zones based on how similar they are (calculated using Euclidean distance). We use Maximum/ Complete linkage clustering as this tends to produce more compact clusters.


set.seed(926)

df_vars3 <- df_vars2
df.dist <- dist(df_vars3[,2:ncol(df_vars3)], method = 'euclidean')
hc <- hclust(df.dist, method = 'ward.D2')

hcd <- as.dendrogram(hc)

plot(hc, labels = df_vars3$zone_code,hang = -1, cex = 0.4, xlab = "Zones")

op = par(bg = "#d7e0df")

plot(cut(hcd, h = 1.75)$upper, main = "upper tree",
     col.main = "#45ADA8", col.lab = "#7C8071", 
     col.axis = "#F38630", lwd = 3, lty = 3, sub = "")
# add axis
axis(side = 2, at = seq(0, 400, 100), col = "#F38630", labels = FALSE,
    lwd = 2)
# add text in margin
mtext(seq(0, 400, 100), side = 2, at = seq(0, 400, 100), line = 1,
    col = "#A38630", las = 2)

NA
NA
NA
NA

The full dendrogram is too large for discerning anything meaningful, so we display the upper part of the tree, where h > 2. The optimal cutoff height seems to be around 4 - this gets us 5 clusters. Cutting the dendrogram any higher would result in 3 clusters (too little).

Note: We did attempt kmeans with 6 clusters, but this ended up splitting Cluster 1 (Momentum - New Launch and Relaunched) into 2 segments. We settled on 5 clusters, as it did not make business sense to have 2 clusters catering to new zones.


sub_grp <- cutree(hc, k = 5)

df_final2 <-  cbind(df, cluster = sub_grp)
fviz_cluster(list(data = df_vars3[,2:ncol(df_vars3)], cluster = sub_grp), labelsize = 10)


df_summ2 <- df_final2 %>%
  group_by(cluster) %>%
  summarise(number_of_zones = n(),
            zone_tenure_days = round(mean(zone_tenure_days)),
            zone_population = round(mean(zone_population)),
            #area = mean(area),
            popn_density = round(mean(popn_density)),
            zones_in_city = round(mean(zones_in_city)),
            live_rx_total = round(mean(live_rx_total)),
            number_cuisines = mean(number_cuisines),
            #rx_per_thousand_popn = mean(rx_per_thousand_popn),
            has_editions_site = mean(has_editions_site),
            pct_mplus = mean(pct_mplus),
            avg_price_category = mean(avg_price_category),
            car_deliv_pct = mean(car_deliv_pct),

            comp_justeat_bool = mean(comp_justeat_bool),
            comp_uber_bool = mean(comp_uber_bool),
            comp_justeat_rx_supply = round(mean(comp_justeat_rx_supply)),
            comp_uber_rx_supply = round(mean(comp_uber_rx_supply)),
            # num_rx_g6 = mean(num_rx_g6),
            # num_rx_enterprise = mean(num_rx_enterprise),
            # num_rx_local = mean(num_rx_local),
            num_rx_g25_pct = mean(num_rx_g25_pct),
            num_rx_enterprise_pct = mean(num_rx_enterprise_pct),
            #num_rx_local_pct = mean(num_rx_local_pct),
            #num_rx_nosegment_pct = mean(num_rx_nosegment_pct),
            # aof = mean(aof),
            # aov = mean(aov),
            #plus_orders_pct = mean(plus_orders_pct),
            # convenience_orders_pct = mean(convenience_orders_pct),
    
            # bicycle_deliv_pct = mean (bicycle_deliv_pct),
            # scooter_deliv_pct = mean(scooter_deliv_pct),
            # null_deliv_pct = mean(null_deliv_pct),
            # num_alcohol_rx_pct   = mean(num_alcohol_rx_pct),
            # num_american_rx_pct = mean(num_american_rx_pct),
            # num_british_rx_pct = mean(num_british_rx_pct ),
            # num_chinese_rx_pct = mean(num_chinese_rx_pct), 
            # num_dessert_rx_pct = mean(num_dessert_rx_pct),
            # num_french_rx_pct = mean(num_french_rx_pct),
            # num_greek_rx_pct = mean(num_greek_rx_pct),
            # num_indian_rx_pct  = mean(num_indian_rx_pct ),
            # num_italian_rx_pct = mean(num_italian_rx_pct),
            # num_japanese_rx_pct = mean(num_japanese_rx_pct), 
            # num_korean_rx_pct  = mean(num_korean_rx_pct ),
            # num_lebanese_rx_pct  = mean(num_lebanese_rx_pct), 
            # num_mexican_rx_pct = mean(num_mexican_rx_pct ),
            # num_spanish_rx_pct = mean(num_spanish_rx_pct),
            # num_thai_rx_pct = mean(num_thai_rx_pct),
            # num_turkish_rx_pct = mean(num_turkish_rx_pct),
            # num_vietnamese_rx_pct = mean(num_vietnamese_rx_pct),
            # num_missing_rx_pct = mean(num_missing_rx_pct)
            )
  
nums <- vapply(df_summ2, is.numeric, FUN.VALUE = logical(1))
df_summ2[,nums] <- round(df_summ2[,nums], digits = 2)

test <- as.data.frame(t(df_summ2))
names(test) <- c('Cluster 1', 'Cluster 2', 'Cluster 3', 'Cluster 4', 'Cluster 5')
test <- test[-1,]

test
NA
NA

We now bootstrap our dataset to see how stable our clusters remain over time.

start <- Sys.time()

cluster_list <- list()
cluster_df_list <- list()
pairwise_list_boot <- list()

k_hc <- 5
sample_size <- nrow(df_vars3)
n_iter <- 1:250
n_clust <- 1:k_hc
  
for (i in n_iter) {

  rows_to_sample <- sample(c(1:sample_size), replace = T)
  df_boot <- df_vars3[rows_to_sample,]
  
  #df_boot <- df_vars2
  
    if(i == 1) {
      df.dist.boot <- df.dist
      hc.boot <- hc

    } else {
      set.seed(sample(c(1:1000),1))
      df.dist.boot <- dist(df_boot[,2:ncol(df_boot)], method = 'euclidean')
      hc.boot  <- hclust(df.dist.boot, method = 'ward.D2')

  }
  
  sub_grp.boot <- cutree(hc.boot, k = k_hc)
  
  zone_code_names <- hc.boot$labels
  zone_code_names <- sub(".[1-9]+", "", zone_code_names) #to prevent re-naming of repeated zones
  
  clusters <- data.frame(zone_code = zone_code_names,
                         cluster = sub_grp.boot,
                         iteration = i)
  clusters$zone_code <- as.character(clusters$zone_code)

  
  pairwise_list_boot.sub <- list()
  
  for (m in n_clust) {
    sub.clust <- clusters$zone_code[clusters$cluster == m]
    for (n in 1:length(sub.clust)) {
      for (p in 1:length(sub.clust)) {
      pair <- paste(sort(c(sub.clust[n], sub.clust[p])), collapse = "-")
      pairwise_list_boot.sub[[pair]] <- pair
    }
    }

  }

      # for (m in 1:nrow(clusters)) {
      #   for( n in 1:nrow(clusters)) {
      #     if (clusters[m,2] == clusters[n,2]) {
      #       pair <- paste(sort(c(clusters[m,1], clusters[n,1])), collapse = "-")
      #       pairwise_list_boot.sub[[pair]] <- pair
      #     }
      #   }
      # }
  
  pairwise_list_boot[[length(pairwise_list_boot)+ 1]] <- pairwise_list_boot.sub
  
  for (n in n_clust) {
    cluster_list[[paste0(i, "-", n)]] <- as.vector(clusters$zone_code[clusters$cluster == n])
  }
  cluster_df_list[[i]] <- clusters

  paste0("Completed: Iteration ",i)
}

end <- Sys.time()
  
time.taken <- end - start  
  

df_final_boot <- do.call(rbind, cluster_df_list)

numer_list <- list()
denom_list <- list()

for (i in 2:length(pairwise_list_boot)) {
  orig <- pairwise_list_boot[[1]]
  test <- pairwise_list_boot[[i]]
  numer <- sum(test %in% orig)
  denom <- length(test)
  numer_list [[length(numer_list )+1]] <- numer
  denom_list [[length(denom_list )+1]] <- denom
  }

numer_df <- t(data.frame(numer_list))
denom_df <- t(data.frame(denom_list))
iter_df <- data.frame(t = c(1:length(numer_df)),
                      orig_pairs = numer_df,
                      total_pairs = denom_df)

iter_df$cumu_orig_pairs <- cumsum(iter_df$orig_pairs)
iter_df$cumu_total_pairs <- cumsum(iter_df$total_pairs)
iter_df$mismatch <- with(iter_df, 1- (cumu_orig_pairs/cumu_total_pairs))

iter_df_hclust <- iter_df

We see that our hierarchical clusters are also highly stable, and actually marginally more stable than to our kmeans clusters. Over time, around 11.7% of our zone-pairs end up segmented into different clusters. This means that 88.3% of our zone-pairs remain in the same cluster. While hierarchical clustering gave us higher cluster stability, we decided to go with kmeans for the following reasons: * The 2 methodologies led to basically the same clusters! * The difference in performance was marginal (1.38 pp) in terms of cluster stability *Hierarchical clustering is more computationally expensive

plot_ly( x = iter_df_hclust$t, y = iter_df_hclust$mismatch, mode = 'lines', type = 'scatter') %>%
  layout(title = "% of incorrect pairwise matches over time",
         xaxis = list(title = "Number of Iterations"), 
         yaxis = list(title = "% Incorrect Matches", range = c(0,0.3)))

# cb1 <- clusterboot(df_vars2[2:ncol(df_vars2)], B = 100, bootmethod = 'boot', bscompare = T,
#                    multipleboot = T, clustermethod = kmeansCBI, dissolution = 0.5, krange = 5,
#                    seed = 18)
# 
# df_cb <- cb1$result$result
# groups<-cb1$result$partition
# cb1$bootmean
# cb1$bootbrd


# 
# iter_l <- data.frame(cluster_first = integer(),
#                    cluster_second = integer(),
#                    J.dist = numeric())
# 
# l <- 1:length(cluster_list)
# 
# for (i in n_clust) {
#   x <- cluster_list[[i]]
#   
#   for(c in (l[!(l %in% i)]) ) {
#     y <- cluster_list[[c]]
# 
#     t <- data.frame(cluster_first = names(cluster_list)[[i]], 
#                    cluster_second = names(cluster_list)[[c]],
#                    J.dist = length(unique(x[x %in% y]))/length(unique(c(x, y)))       )
#     
#      
#     iter_l <- rbind(iter_l,t)
#     
#   }
#   
# }
# 
# 
# 
# iter_l$iter_x <- as.numeric(sub("-.+", "", iter_l$cluster_first))
# iter_l$clus_x <- as.numeric(sub(".+-", "", iter_l$cluster_first))
# 
# iter_l$iter_y<- as.numeric(sub("-.+", "", iter_l$cluster_second))
# iter_l$clus_y <- as.numeric(sub(".+-", "", iter_l$cluster_second))
# 
# iter_summ <- iter_l %>%
#   group_by(iter_x, clus_x, iter_y) %>%
#   mutate(max_Jdist = max(J.dist)) 
# 
# iter_summ <- iter_summ %>%
#   filter(J.dist == max_Jdist) %>%
#   filter(iter_x != iter_y) %>%
#   mutate(cluster_dissolved = ifelse(J.dist < 0.5, 1, 0))
# 
# iter_summ <- iter_summ[order(iter_summ$iter_x, iter_summ$iter_y, iter_summ$clus_x),]
#          
# iter_summ$cum_sum_cluster_dissolved <- cumsum(iter_summ$cluster_dissolved)
# iter_summ$num <- 1
# iter_summ$total_clusters <- cumsum(iter_summ$num)
# 
# iter_comp <- iter_summ[iter_summ$clus_x == k,] #ie obtain the cumulative number of dissolved clusters, and total clusters, at the end of each iteration
# 
#   
# iter_comp$pct_dissolved <- iter_comp$cum_sum_cluster_dissolved/iter_comp$total_clusters
LS0tCnRpdGxlOiAiWm9uZSBTZWdtZW50YXRpb24gVjIiCmF1dGhvcjogIlJpdmFsaSBEYXNzIgpkYXRlOiAiMTcvMDMvMjAyMCIKb3V0cHV0OiAKICAgIGh0bWxfbm90ZWJvb2s6CiAgICAgIGNvZGVfZm9sZGluZzogaGlkZQogICAgICBzbWFydDogZmFsc2UKICAgIGh0bWxfZG9jdW1lbnQ6IGRlZmF1bHQKICAgIHBkZl9kb2N1bWVudDogZGVmYXVsdAotLS0KCgo8YSBuYW1lPSJpbnRyX2FuY2hvciI+PC9hPiAKCjwvYnI+CjwhLS0gPGltZyBzcmM9Imh0dHBzOi8vczMtZXUtY2VudHJhbC0xLmFtYXpvbmF3cy5jb20vY2VudGF1ci13cC9jcmVhdGl2ZXJldmlldy9wcm9kL2NvbnRlbnQvdXBsb2Fkcy8yMDE2LzA5L0RlbGl2ZXJvby1Mb2dvLUNyb3AucG5nIiBoZWlnaHQ9IjEwMCIgd2lkdGg9IjEwMCIgc3R5bGU9ImZsb2F0OnJpZ2h0Ij4gLS0+CjxhIG5hbWU9ImNvbnRlbnRzX2FuY2hvciI+PC9hPgo8ZGl2IHN0eWxlID0gImNvbG9yOnJlZDsgZm9udC13ZWlnaHQ6IGJvbGQ7IGZvbnQtc2l6ZToxNHB0Ij4gClRoaXMgbW9kZWwgd2FzIGxhc3QgcnVuIG9uIGByIFN5cy50aW1lKClgCgo8L2Rpdj4KCiMjIENvbnRlbnRzICAKCltCYWNrZ3JvdW5kIGFuZCBTY29wZV0oI2FpbV9hbmNob3IpCgpbRmVhdHVyZSBTZWxlY3Rpb25dKCNmZWF0X2FuY2hvcikKCltDbHVzdGVyaW5nIC0gS21lYW5zXSgjY2x1czFfYW5jaG9yKQoKW0Jvb3RzdHJhcHBpbmcgLSBLbWVhbnNdKCNib290MV9hbmNob3IpCgpbRmluYWwgT3V0cHV0c10oI2Zpb3BfYW5jaG9yKQoKW0FwcGVuZGl4XSgjYXBwZTFfYW5jaG9yKQoKW0NsdXN0ZXJpbmcgLSBIaWVyYXJjaGljYWwgQ2x1c3RlcmluZ10oI2NsdXMyX2FuY2hvcikKCltCb290c3RyYXBwaW5nIC0gSGllcmFyY2hpY2FsIENsdXN0ZXJpbmddKCNib290Ml9hbmNob3IpCgoKPGEgbmFtZT0iYWltX2FuY2hvciI+PC9hPiAKCiMjIyBCYWNrZ3JvdW5kIGFuZCBTY29wZQoKRGlmZmVyZW50IHRlYW1zIGF0IERlbGl2ZXJvbyBoYXZlIGNvbWUgdXAgd2l0aCBkaWZmZXJlbnQgem9uZSBzZWdtZW50YXRpb24gbWV0aG9kcyBpbiB0aGUgcGFzdC4gSG93ZXZlciwgYWxsIG9mIHRoZXNlIGhhdmUgYmVlbiBtYWRlIHdpdGggYSBzcGVjaWZpYyBhZ2VuZGEuIEZvciBleGFtcGxlLCBQcm9qZWN0IEhvcHBlciBoYXMgc2VnbWVudGVkIHpvbmVzIGJhc2VkIG9uIHRoZSBjb3N0cyByZXF1aXJlZCB0byBpbXByb3ZlIHRoZWlyIHNlbGVjdGlvbi1zZXJ2aWNlIHRyYWRlIG9mZiwgd2hpbGUgdGhlIFByaWNpbmcgdGVhbSBoYXMgc2VnbWVudGVkIHpvbmVzIGJhc2VkIG9uIHByaWNlLXJlbGF0ZWQgZmFjdG9ycy4gCiAgClRoZSAqKmFpbSoqIGhlcmUgaXMgdG8gY29tZSB1cCB3aXRoIGEgKipub24tc3RyYXRlZ2ljKiogc2V0IG9mIHpvbmUgY2x1c3RlcnMsIHdoaWNoIGNsdXN0ZXJzIHpvbmVzIGFjY29yZGluZyB0byB0aGVpciBmdW5kYW1lbnRhbCBmZWF0dXJlcy4gCiAgClRoZSAqKnNjb3BlKiogb2YgdGhpcyBhbmFseXNpcyBpcyB0byBzZWdtZW50IHpvbmVzIGluIFJVSywgYmVmb3JlIGF1dG9tYXRpbmcgdGhlIHNlZ21lbnRhdGlvbiBtZXRob2QgYWNyb3NzIGRpZmZlcmVudCBjb3VudHJpZXMuIEFsbCBmZWF0dXJlcyB3ZXJlIGNvbXB1dGVkIHVzaW5nIGRhdGEgYmV0d2VlbiAxc3QgSmFudWFyeSBhbmQgMjl0aCBGZWJydWFyeSAyMDIwLiBUaGlzIHdhcyB0byBhdm9pZCBhYm5vcm1hbCBiZWhhdmlvdXIgZHVlIHRvIHRoZSBDb3JvbmF2aXJ1cyBvdXRicmVhay4gQ29tcGV0aXRvciBmZWF0dXJlcyB3ZXJlIGNvbXB1dGVkIHVzaW5nIGRhdGEgYmV0d2VlbiAxc3QgYW5kIDd0aCBNYXJjaCAyMDIwLCBkdWUgdG8gdW5yZWxpYWJsZSBVYmVyZWF0cyBzY3JhcGVzIGJlZm9yZSBNYXJjaC4KIAo8YnI+Cjxicj4KYGBge3IgbGlicmFyaWVzX2FuZF9mdW5jdGlvbnMsIGV2YWwgPSBGLCBlY2hvPUYsIG1lc3NhZ2U9RkFMU0V9CmxpYnJhcnkoZWNvbm9tZXRyaWNzKSAjIHJvbyBwYWNrYWdlCmxpYnJhcnkoY3ZyLmRyaXZlci5pbmZlcmVuY2UpCmxpYnJhcnkocGxvdGx5KQpsaWJyYXJ5KHRpZHlyKQpsaWJyYXJ5KHNub3dmbGFrZS5jb25uZWN0b3IpCmxpYnJhcnkoZ2dmb3J0aWZ5KQpsaWJyYXJ5KGNvcnJwbG90KQpsaWJyYXJ5KFJDb2xvckJyZXdlcikKbGlicmFyeShmYWN0b2V4dHJhKQoKcXVlcnkgPC0gIlNFTEVDVCAqIAogICAgICAgICAgRlJPTSBzY3JhdGNoLmFnZ3JlZ2F0ZS56b25lX3ZhcnMKICAgICAgICAgIFdIRVJFIGJ1c2luZXNzX3VuaXRfbmFtZSA9ICdSVUsnOyIKCmRmIDwtIHNub3dmbGFrZS5jb25uZWN0b3I6OnJ1bl9zcWxfcXVlcmllcyhxdWVyeSlbWzFdXSRyZXN1bHQKbmFtZXMoZGYpIDwtIHRvbG93ZXIobmFtZXMoZGYpKQoKZGZbaXMubmEoZGYpXSA8LSAwCgpgYGAKCmBgYCB7ciBmZWF0dXJlX2VuZ2luZWVyaW5nfQoKI2RmJGNvbXBfdWJlcl9yeF9zdXBwbHlbaXMubmEoZGYkY29tcF91YmVyX3J4X3N1cHBseSldIDwtIDAKZGYkcGN0X21wbHVzIDwtIGRmJHJ4X21wbHVzL2RmJGxpdmVfcnhfdG90YWwKZGYkYW9mIDwtIGRmJGFvZi8yICNzaWxseSBzcWwgbWlzdGFrZSBzb3JyeQoKZGYkem9uZV90ZW51cmVfZGF5c1tkZiR6b25lX2NvZGUgPT0gJ0xWTiddIDwtICBhcy5udW1lcmljKGFzLkRhdGUoJzIwMjAtMDMtMTQnKSAtIGFzLkRhdGUoJzIwMjAtMDEtMDknKSkKCiNkZiRjb21wX251bWJlcl9jb21wZXRpdG9ycyA8LSBkZiRjb21wX2p1c3RlYXRfYm9vbCArIGRmJGNvbXBfdWJlcl9ib29sCgoKZmFjdG9yc19kZW1vIDwtIGMoJ3pvbmVfdGVudXJlX2RheXMnLCAnem9uZV9wb3B1bGF0aW9uJywgJ2FyZWEnLCAncG9wbl9kZW5zaXR5JywgJ3pvbmVzX2luX2NpdHknLCAncGN0X3pvbmVfb2ZfY2l0eScpCmZhY3RvcnNfcnhfc3VwcGx5IDwtIGMoJ2xpdmVfcnhfdG90YWwnLCAncnhfcGVyX3Rob3VzYW5kX3BvcG4nLCAnaGFzX2VkaXRpb25zX3NpdGUnLCAicnhfY29yZSIsICJyeF9lZGl0aW9ucyIsICJyeF9tcGx1cyIsICJwY3RfbXBsdXMiLCAnYXZnX3ByaWNlX2NhdGVnb3J5JywgJ251bWJlcl9jdWlzaW5lcycpCmZhY3RvcnNfcnhfY3Vpc2luZSA8LSBuYW1lcyhkZilbZ3JlcGwoIm51bV8uK19yeCIsIG5hbWVzKGRmKSldCmZhY3RvcnNfcnhfdHlwZSA8LSBuYW1lcyhkZilbZ3JlcGwoIm51bV9yeC4rIiwgbmFtZXMoZGYpKV0KZmFjdG9yc19jb21wIDwtICBuYW1lcyhkZilbZ3JlcGwoImNvbXAuKyIsIG5hbWVzKGRmKSldCmZhY3RvcnNfY3VzdCA8LSBjKCJvcmRlcnMiLCAiY3VzdG9tZXJzIiwgImN1c3RvbWVyc19uZXciLCAiY3VzdG9tZXJzX2V4aXN0aW5nIiwgImFvZiIsICJhb3YiLCAiYXZnX2NyZWRpdHNfdm91Y2hlcnMiLCAicGx1c19vcmRlcnNfcGN0IiwgImNvbnZlbmllbmNlX29yZGVyc19wY3QiKQpmYWN0b3JzX3JpZGVycyA8LSBjKCJjbnRfcmlkZXJzXzN3IiwgInNjb290ZXJfZGVsaXZfcGN0IiwgImJpY3ljbGVfZGVsaXZfcGN0IiwgImNhcl9kZWxpdl9wY3QiKQoKbGl2ZV9yeF9jYyA8LSByb3dTdW1zKGRmWyxmYWN0b3JzX3J4X2N1aXNpbmVdKQpmb3IoaSBpbiBmYWN0b3JzX3J4X2N1aXNpbmUpIHsKICBkZltbcGFzdGUwKGksIl9wY3QiKV1dIDwtIGRmW1tpXV0vIGxpdmVfcnhfY2MKfQoKbGl2ZV9yeF90eXBlIDwtIHJvd1N1bXMoZGZbLGZhY3RvcnNfcnhfdHlwZV0pIApmb3IoaSBpbiBmYWN0b3JzX3J4X3R5cGUpIHsKICBkZltbcGFzdGUwKGksIl9wY3QiKV1dIDwtIGRmW1tpXV0vIGxpdmVfcnhfdHlwZQp9CgpkZiRudW1fcnhfZW50ZXJwcmlzZV9wY3QgPC0gZGYkbnVtX3J4X2VudGdvbGRfcGN0ICsgZGYkbnVtX3J4X2VudHNpbHZfcGN0CmRmJG51bV9yeF9sb2NhbF9wY3QgPC0gZGYkbnVtX3J4X2xvZ29sZF9wY3QgKyBkZiRudW1fcnhfbG9zaWx2X3BjdCArIGRmJG51bV9yeF9sb2Jyb25fcGN0CgpmYWN0b3JzX3J4X2N1aXNpbmUgPC0gbmFtZXMoZGYpW2dyZXBsKCJudW1fLitfcnhfcGN0IiwgbmFtZXMoZGYpKV0KZmFjdG9yc19yeF90eXBlIDwtIG5hbWVzKGRmKVtncmVwbCgibnVtX3J4Xy4rX3BjdCIsIG5hbWVzKGRmKSldCgoKYGBgCjxhIG5hbWU9ImZlYXRfYW5jaG9yIj48L2E+IAoKIyMjIEZlYXR1cmUgU2VsZWN0aW9uIAoKV2UgZ3JvdXAgZmVhdHVyZXMgaW50byB0aGUgZm9sbG93aW5nIHNlZ21lbnRzOgoKMS4gKipEZW1vZ3JhcGhpYyBmZWF0dXJlczoqKiBab25lIGFyZWEsIHBvcHVsYXRpb24gZGVuc2l0eSwgbnVtYmVyIG9mIHpvbmVzIGluIGNpdHksIGV0YwoyLiAqKlJlc3RhdXJhbnQgc3VwcGx5IGZlYXR1cmVzOioqIE51bWJlciBvZiBsaXZlIHJlc3RhdXJhbnRzLCBudW1iZXIgb2YgY29yZSByZXN0YXVyYW50cywgem9uZSBhdmVyYWdlIHByaWNlIGNhdGVnb3J5LCBldGMuCjMuICoqUmVzdGF1cmFudCBjdWlzaW5lIGZlYXR1cmVzOioqIE51bWJlciBvZiByZXN0YXVyYW50cyBhdmFpbGFibGUgZm9yIGVhY2ggRGVsaXZlcm9vIGN1aXNpbmUgY2F0ZWdvcnkKNC4gKipDb21wZXRpdG9yIGZlYXR1cmVzOioqIEJvb2xlYW5zIGZvciB3aGljaCBjb21wZXRpdG9ycyBhcmUgYWN0aXZlIGluIHRoZSB6b25lLCB0b3RhbCByZXN0YXVyYW50IHN1cHBseSBwZXIgY29tcGV0aXRvcgo1LiAqKkN1c3RvbWVyIGZlYXR1cmVzOioqIE5ldyBjdXN0b21lcnMsIGV4aXN0aW5nIGN1c3RvbWVycywgQU9GLCBBT1YsIHBlcmNlbnRhZ2Ugb2YgcGx1cyBvcmRlcnMsIGV0Yy4KNi4gKipSaWRlciBmZWF0dXJlczoqKiBOdW1iZXIgb2YgdG90YWwgcmlkZXJzLCBwZXJjZW50YWdlIHNwbGl0IGJ5IHZlaGljbGUsIGV0Yy4KCldlIGRvIG5vdCB1c2UgZmVhdHVyZXMgbGlrZSBFT0QgYW5kIG51bWJlciBvZiBCMTBzLCBhcyB0aGVzZSBhcmUgbGV2ZXJzIHRoYXQgY2FuIGJlIGVhc2lseSBwdWxsZWQgYnkgRGVsaXZlcm9vLCByYXRoZXIgdGhhbiB1bmRlcmx5aW5nIGZlYXR1cmVzIG9mIGEgem9uZS4gV2UgdWx0aW1hdGVseSBuYXJyb3dlZCB0aGVzZSBmZWF0dXJlcyBkb3duIGludG8gMTIgdmFyaWFibGVzIHRoYXQgd2UgdXNlZCwgdG8gbGVlcCB0aGUgbW9kZWwgcGFyc2ltb25pb3VzLgoKV2Ugbm9ybWFsaXplIGVhY2ggdmFyaWFibGUsIGFuZCB1c2UgY29ycmVsYXRpb24gbWF0cmljZXMgdG8gcmVtb3ZlIGZlYXR1cmVzIHRoYXQgYXJlIGhpZ2hseSBjb3JyZWxhdGVkIHRvIGVhY2ggb3RoZXIuIEJlbG93IGlzIHRoZSBjb3JyZWxhdGlvbiBtYXRyaXggZm9yIHJlc3RhdXJhbnQtcmVsYXRlZCBmYWN0b3JzLgoKYGBge3Igc2VsZWN0X3ZhcnMsIGZpZy5oZWlnaHQ9IDYsIGZpZy53aWR0aD0gOCwgZmlnLmFsaWduID0gVCB9CmRmX3ZhcnMgPC0gZGZbLGMoZmFjdG9yc19kZW1vLCBmYWN0b3JzX3J4X3N1cHBseSldCgoKbm9ybWFsaXplIDwtIGZ1bmN0aW9uKHgpIHsKcmV0dXJuICgoeCAtIG1pbih4KSkgLyAobWF4KHgpIC0gbWluKHgpKSkgICAgICAgICAgICAgICAgICAgICAgICAKfQoKCmNvbHNfdG9fbm9ybWFsaXplIDwtIG5hbWVzKGRmX3ZhcnMpWzE6bmNvbChkZl92YXJzKV0KCmZvciAoaSBpbiBjb2xzX3RvX25vcm1hbGl6ZSkgewogIGRmX3ZhcnNbW3Bhc3RlMCgibm9ybV8iLCBpKV1dIDwtIG5vcm1hbGl6ZShkZl92YXJzW1tpXV0pCn0KCmRmX3ZhcnMyIDwtIGRmX3ZhcnNbLC13aGljaChuYW1lcyhkZl92YXJzKSAlaW4lIGNvbHNfdG9fbm9ybWFsaXplKV0KbmFtZXMoZGZfdmFyczIpIDwtIGdzdWIoIm5vcm1fIiwgIiIsIG5hbWVzKGRmX3ZhcnMyKSkKCgpjIDwtIGNvcihkZl92YXJzMlssMTpuY29sKGRmX3ZhcnMyKV0pCgpjb3JycGxvdChjLCBtZXRob2QgPSAibnVtYmVyIiwgdHlwZT0idXBwZXIiLCBvcmRlcj0ib3JpZ2luYWwiLAogICAgICAgICBjb2w9YnJld2VyLnBhbChuPTgsIG5hbWU9IlJkWWxCdSIpLCB0bC5jZXggPTAuOCwgbnVtYmVyLmNleCA9IDAuNikKCgoKYGBgCkhlcmUgaXMgYSBjb3JyZWxhdGlvbiBtYXRyaXggd2l0aCB0aGUgbWFpbiBkZW1vZ3JhcGhpYyBhbmQgY3VzdG9tZXIgZmVhdHVyZXMuCgpXZSB0aHVzIHJlbW92ZSBmZWF0dXJlcyB3aXRoIGEgY29ycmVsYXRpb24gc2NvcmUgYWJvdmUgMC43LiBUaGVzZSBhcmU6IHBlcmNlbnRhZ2UgcG9wdWxhdGlvbiBvZiB6b25lIHZzIGNpdHkgLCB0b3RhbCBjb3JlIHJlc3RhdXJhbnRzLCBudW1iZXIgb2YgbXBsdXMgcmVzdGF1cmFudHMsIGV4aXN0aW5nIGN1c3RvbWVycywgZXRjLiBGb3IgcmVzdGF1cmFudCB0eXBlLCB3ZSBvbmx5IGtlZXAgZmVhdHVyZXMgZm9yIEc2LCBjb21iaW5lZCBlbnRlcnByaXNlIChHb2xkLCBTaWx2ZXIpIGFuZCBjb21iaW5lZCBsb2NhbCByZXN0YXVyYW50cy4KCkp1c3QgYSBub3RlLCBmb3IgdmFyaWFibGVzIGFyb3VuZCByaWRlciB2ZWhpY2xlIHR5cGUsIHNvbWUgem9uZXMgaGF2ZSBhbGwgdmVoaWNsZXMgY2xhc3NpZmllZCBhcyBOVUxMLiBUaGVzZSBhcmUgcHJlZG9taW5hbnRseSBNYXJrZXRwbGFjZS1vbmx5IHpvbmVzLgoKYGBge3Igc2VsZWN0X3ZhcnNfMn0KCmRmX3ZhcnMgPC0gZGZbLGMoInpvbmVfY29kZSIsIGZhY3RvcnNfZGVtbywgZmFjdG9yc19yeF9zdXBwbHksICJudW1fcnhfZzI1X3BjdCIsICJudW1fcnhfZW50ZXJwcmlzZV9wY3QiLCBmYWN0b3JzX2NvbXAsIGZhY3RvcnNfcmlkZXJzKV0KCmNvbHNfdG9fcmVtb3ZlIDwtIGMoJ3BjdF96b25lX29mX2NpdHknLCAncnhfY29yZScsICdvcmRlcnMnLCAnY3VzdG9tZXJzJywgJ2N1c3RvbWVyc19leGlzdGluZycsICdyeF9tcGx1cycsICdyeF9lZGl0aW9ucycsICdudW1fYWZyaWNhbl9yeF9wY3QnLCAnbnVtX21hbGF5X3J4X3BjdCcsICdjbnRfcmlkZXJzXzN3JywgJ3J4X3Blcl90aG91c2FuZF9wb3BuJywgJ2F2Z19wcmljZV9jYXRlZ29yeScsICdzY29vdGVyX2RlbGl2X3BjdCcsICdiaWN5Y2xlX2RlbGl2X3BjdCcsICJudW1fcnhfbG9jYWxfcGN0IiwgImhhc19lZGl0aW9uc19zaXRlIiwgJ2NvbXBfanVzdGVhdF9ib29sJywKICAgICAgICAgICAgICAgICAgICAnY29tcF91YmVyX2Jvb2wnLCAiYXJlYSIpCmRmX3ZhcnMgPC0gZGZfdmFyc1ssLXdoaWNoKG5hbWVzKGRmX3ZhcnMpICVpbiUgY29sc190b19yZW1vdmUpXQogICAgICAgIApjb2xzX3RvX25vcm1hbGl6ZSA8LSBuYW1lcyhkZl92YXJzKVsyOm5jb2woZGZfdmFycyldCgpmb3IgKGkgaW4gY29sc190b19ub3JtYWxpemUpIHsKICBkZl92YXJzW1twYXN0ZTAoIm5vcm1fIiwgaSldXSA8LSBub3JtYWxpemUoZGZfdmFyc1tbaV1dKQp9CgpkZl92YXJzMiA8LSBkZl92YXJzWywtd2hpY2gobmFtZXMoZGZfdmFycykgJWluJSBjb2xzX3RvX25vcm1hbGl6ZSldCm5hbWVzKGRmX3ZhcnMyKSA8LSBnc3ViKCJub3JtXyIsICIiLCBuYW1lcyhkZl92YXJzMikpCiNkZl92YXJzMiA8LSBzdWJzZXQoZGZfdmFyczIsIHNlbGVjdCA9IC0gYyhudW1fYWZyaWNhbl9yeF9wY3QsIG51bV9tYWxheV9yeF9wY3QpKQoKYGBgCgpXZSBkbyBhIHF1aWNrIFByaW5jaXBhbCBDb21wb25lbnQgQW5hbHlzaXMgKFBDQSkgdG8gdW5kZXJzdGFuZCB3aGljaCBmZWF0dXJlcyBhcmUgY29ycmVsYXRlZCwgYW5kIGFsc28gc2VlIHdoYXQgZGlmZmVyZW50aWF0ZXMgZGlmZmVyZW50IHpvbmVzIGZyb20gZWFjaCBvdGhlci4gVGhlIGZpcnN0IDIgcHJpbmNpcGFsIGNvbXBvbmVudHMgZXhwbGFpbiAqKmByIHN1bW1hcnkoZGYucGNhKVtbNl1dWzMsMl1gKiogb2YgdGhlIHRvdGFsIHZhcmlhdGlvbiwgc28gZG8ga2VlcCBpbiBtaW5kIHRoYXQgdGhlcmUgYXJlIG90aGVyIG9ydGhvZ29uYWwgcHJpbmNpcGFsIGNvbXBvbmVudHMgd2UgaGF2ZSBub3QgdmlzdWFsaXNlZC4KCkJlbG93IGlzIGEgYmlwbG90IGJhc2VkIG9uIG91ciBkYXRhc2V0LgpgYGB7ciBwY2EsIGZpZy53aWR0aD0gMTAsIGZpZy5oZWlnaHQgPSA2LCBmaWcuYWxpZ249VH0KCnNldC5zZWVkKDEyMzQpCiNkZl92YXJzMiA8LSBkZl92YXJzMlsxOjEwLF0KZGYucGNhIDwtIHByY29tcChkZl92YXJzMlssMjpuY29sKGRmX3ZhcnMyKV0sIGNlbnRlciA9IFQsIHNjYWxlID0gVCkKY29tcCA8LSBjYmluZCh6b25lX2NvZGUgPSBkZl92YXJzMiR6b25lX2NvZGUsIGRhdGEuZnJhbWUoZGYucGNhJHgpKQoKCnJvd25hbWVzKGRmX3ZhcnMyKSA8LSBkZl92YXJzMiR6b25lX2NvZGUKI3N1bW1hcnkoZGYucGNhKQoKI2JpcGxvdChkZi5wY2EpCgojIGF1dG9wbG90KGRmLnBjYSwgc2hhcGUgPSBGLAojICAgICAgICAgZGF0YSA9IGRmX3ZhcnMyLCBsYWJlbCA9IFQsIGxhYmVsLnNpemUgPSAyLAojICAgICAgICAgbG9hZGluZ3MgPSBULCBsb2FkaW5ncy5sYWJlbCA9IFQsIGxvYWRpbmdzLmNvbG91ciA9ICdibHVlJywgbG9hZGluZ3MubGFiZWwuc2l6ZSA9IDQsCiMgICAgICAgICBsb2FkaW5ncy5sYWJlbC52anVzdCA9IDAsIGxvYWRpbmdzLmxhYmVsLmhqdXN0ID0gMCwKIyAgICAgICAgICB4bGltID0gYygtMC4wNiwwLjE2NSksIHlsaW0gPSBjKC0wLjE1LDAuMTc1KSwgcG9zaXRpb24gPSAiaml0dGVyIikKCmF1dG9wbG90KGRmLnBjYSwgZGF0YSA9IGRmX3ZhcnMyLCBsYWJlbCA9IFQsIHNoYXBlID0gRiwgbGFiZWwuc2l6ZSA9IDIuNSwKICAgICAgICAgbG9hZGluZ3MgPSBULCBsb2FkaW5ncy5sYWJlbCA9IFQsIGxvYWRpbmdzLmNvbG91ciA9ICdibHVlJywgbG9hZGluZ3MubGFiZWwuc2l6ZSA9IDQsCmxvYWRpbmdzLmxhYmVsLnZqdXN0ID0gLTEsIGxvYWRpbmdzLmxhYmVsLmhqdXN0ID0gMSkKCgpgYGAKRmFjdG9ycyB0aGF0IGFyZSBtb3N0IGltcG9ydGFudCBmb3IgZGlmZmVyZW50aWF0aW5nIHpvbmVzIGFyZSBudW1iZXIgb2YgbGl2ZSByZXN0YXVyYW50cyBpbiB0aGUgem9uZSwgbnVtYmVyIG9mIGN1aXNpbmVzLCBob3cgbG9uZyB0aGUgem9uZSBoYXMgYmVlbiBvcGVuICh6b25lX3RlbnVyZSksIHRoZSBwZXJjYWVudGFnZSBvZiBNKyByZXN0YXVyYW50cywgSnVzdEVhdCBhbmQgVWJlcidzIHJlc3RhdXJhbnQgc3VwcGx5LCBldGMuCgpTb21lIGZhY3RvcnMgYXJlIHNpZ25pZmljYW50bHkgY29ycmVsYXRlZCB0byB6b25lIHNpemUgYW5kIGRlbnNpdHksIHN1Y2ggYXMgVWJlckVhdCdzIHJlc3RhdXJhbnQgc3VwcGx5LgoKVGhlIGJpcGxvdCBhbGxvd3MgdXMgdG8gc2VlIHdoYXQgbWFrZXMgMiB6b25lcyBzaW1pbGFyIG9yIGRpc3NpbWlsYXIuIEZvciBleGFtcGxlLCAqKk1DIChNYW5jaGVzdGVyIENlbnRyYWwpKiogYW5kICoqQk5DIChCcmlnaHRvbiBDZW50cmFsKSoqIGFyZSBwcmV0dHkgc2ltaWxhciBhbG9uZyBmZWF0dXJlcyB0aGF0IGFmZmVjdCBQQzEgLSBoaWdoIHpvbmUgdGVudXJlLCBoaWdoIG51bWJlciBvZiBjdWlzaW5lcywgaGlnaCByZXN0YXVyYW50IHN1cHBseSwgYm90aCBoYXZlIEVkaXRpb25zIHNpdGVzLCBldGMuIFdoYXQgZGlmZmVyZW50aWF0ZXMgdGhlbSBpcyB0aGUgbnVtYmVyIG9mIHpvbmVzIGluIHRoZWlyIHJlc3BlY3RpdmUgY2l0eS9tZXRyb3BvbGl0YW4gYXJlYXMgKDI2IHZzLiAxIHpvbmUpLCBwZXJjZW50YWdlIG9mIGNhciBkZWxpdmVyaWVzIGluIHRoZSB6b25lICgxJSB2cy4gMTElKSwgdGhlaXIgY29tcGV0aXRpdmUgbGFuZHNjYXBlIChKdXN0ZWF0IGFuZCBVYmVyZWF0cyBoYXZlIG11Y2ggaGlnaGVyIHN1cHBseSBpbiBNQywgd2hlcmVhcyBEZWxpdmVyb28gc3VwcGx5IGlzIHJvdWdobHkgZXF1YWwgaW4gYm90aCB6b25lcyksIGV0Yy4KCjxicj4KPGJyPgoKPGEgbmFtZT0iY2x1czFfYW5jaG9yIj48L2E+IAoKIyMjIENsdXN0ZXJpbmcgdXNpbmcgS21lYW5zCgpXZSBub3cgY2x1c3RlciB0aGlzIGRhdGEgdXNpbmcga21lYW5zLiBBIHNjcmVlIHBsb3Qgc2hvd3MgdGhhdCB0aGUgb3B0aW1hbCBudW1iZXIgb2YgY2x1c3RlcnMgaXMgYXJvdW5kIDUgb3IgNiwgYXMgYmV5b25kIHRoYXQsIHRoZSByZWR1Y3Rpb24gaW4gdGhlIHRvdGFsIHdpdGhpbi1jbHVzdGVyIHN1bSBvZiBzcXVhcmVzIHRhcGVycyBvZmYuCgpgYGB7ciBjaG9vc2luZ19rLCBmaWcuaGVpZ2h0PTMsIGZpZy53aWR0aCA9IDEwLCBmaWcuYWxpZ249VH0Kay5tYXggPC0gMTUKCndzcyA8LSBzYXBwbHkoMTprLm1heCwKICAgICAgICAgICAgICBmdW5jdGlvbihrKXtrbWVhbnMoZGZfdmFyczJbLDI6bmNvbChkZl92YXJzMildLCBrLCBuc3RhcnQ9NTAsaXRlci5tYXggPSAxNSApJHRvdC53aXRoaW5zc30pCnBsb3QoMTprLm1heCwgd3NzLAogICAgIHR5cGU9ImIiLCBwY2ggPSAxOSwgZnJhbWUgPSBGQUxTRSwKICAgICB4bGFiPSJOdW1iZXIgb2YgY2x1c3RlcnMgSyIsCiAgICAgeWxhYj0iVG90YWwgd2l0aGluLWNsdXN0ZXJzIHN1bSBvZiBzcXVhcmVzIikKCmBgYAoKV2UgdGhlbiBzZWdtZW50IHRoZSB6b25lcyBpbnRvIDUgY2x1c3RlcnMsIHNlZW4gYmVsb3cuCmBgYHtyIGttZWFucywgZmlnLmhlaWdodD02LCBmaWcud2lkdGggPSAxMCwgZmlnLmFsaWduPVR9CnNlZWQgPC0gc2FtcGxlKGMoMToxMDAwKSwxKQpzZXQuc2VlZCg5MjYpCgoKayA8LSA1CnJvd25hbWVzKGRmX3ZhcnMyKSA8LSBkZl92YXJzMiR6b25lX2NvZGUKb3Aua21lYW5zIDwtIGttZWFucyhkZl92YXJzMlssMjpuY29sKGRmX3ZhcnMyKV0sIGspCgoKY2x1c3RlcnMgPC0gZGF0YS5mcmFtZSh6b25lX2NvZGUgPSByb3duYW1lcyhhcy5kYXRhLmZyYW1lKG9wLmttZWFucyRjbHVzdGVyKSksCiAgICAgICAgICAgICAgICAgICAgICAgY2x1c3RlciA9IG9wLmttZWFucyRjbHVzdGVyKQoKCmRmX2ZpbmFsIDwtIG1lcmdlKGNsdXN0ZXJzLCBkZiwKICAgICAgICAgICAgICAgICAgYnkgPSAnem9uZV9jb2RlJywKICAgICAgICAgICAgICAgICAgYWxsLnggPSBUKQoKCmF1dG9wbG90KG9wLmttZWFucywgZGF0YSA9IGRmX3ZhcnMyLCBsYWJlbCA9IFRSVUUsIGxhYmVsLnNpemUgPSAzCiAgICAgICAgICwgc2hhcGUgPSBGCiAgICAgICAgICwgZnJhbWUgPSBUKQoKI3ByaW50KHNlZWQpCmBgYAoKVGhlIGNsdXN0ZXJzIHJlcHJlc2VudCA1IGdyb3VwcyBvZiB6b25lczogKipVcmJhbiBXaW5uZXJzLCBPdXRlciBDaXR5LCBNb21lbnR1bSAtIE5ldyBMYXVuY2ggYW5kIFJlbGF1bmNoZWQsIE9wcG9ydHVuaXR5IC0gV2VhbHRoeSBhbmQgVW5pdmVyc2l0eSBUb3ducyoqLCBhbmQgKipXaHkgYXJlIHdlIGhlcmU/IC0gTSsgWm9uZXMqKi4gQmVsb3cgaXMgYSBzdW1tYXJ5IG9mIHRoZWlyIG1haW4gY2hhcmFjdGVyaXN0aWNzLgoKYGBge3Iga21lYW5zXzJ9CgpjbHVzMSA8LSBjKCdOaWNrbmFtZTogVXJiYW4gV2lubmVycycKLCAnVmVyeSBsYXJnZSB6b25lIHBvcHVsYXRpb24gYW5kIHZlcnkgaGlnaCBwb3B1bGF0aW9uIGRlbnNpdHknCiwgJ1pvbmUgaXMgPjQgeWVhcnMgb2xkJwosICdNaXggb2YgbGFyZ2UgYW5kIHNtYWxsZXIgbWV0cm9wb2xpdGFuIGFyZWFzIC0gbWFpbmx5IGNpdHkgY2VudHJlcycKLCAnSGlnaGVzdCByeCBzdXBwbHksIHZlcnkgaGlnaCBjdWlzaW5lIGRpdmVyc2l0eSwgbm90IG1hbnkgTSsgcmVzdGF1cmFudHMnCiwgJ0RlbGl2ZXJvbyBkb21pbmF0ZXMgdGhlIGNvbXBldGl0aW9uJwosICdPbmx5IGNsdXN0ZXIgd2l0aCBFZGl0aW9ucyBzaXRlcywgbm90IG1hbnkgTSsgcmVzdGF1cmFudHMnCiwgJ09ubHkgY2x1c3RlciB3aGVyZSBtb3N0IGRlbGl2ZXJpZXMgYXJlIG9uIGJpa2VzL3Njb290ZXJzJwosICdFeGFtcGxlIHpvbmVzOiBCcmlnaHRvbiBDZW50cmFsLCBCaXJtaW5naGFtLCBDYW1icmlkZ2UsIExpdmVycG9vbCBDaXR5IENlbnRyZSwgRWRpbmJ1cmdoIE5vcnRoL1NvdXRoLCBQb3J0c21vdXRoJwosICcnKQoKCmNsdXMyIDwtIGMoJ05pY2tuYW1lOiBPcHBvcnR1bml0eScKLCAnTWVkaXVtIHpvbmUgcG9wdWxhdGlvbicKLCAnTW9zdCB6b25lcyBhcmUgfiAzIHllYXJzIG9sZCcKLCAnU21hbGwgbWV0cm9wb2xpdGFuIGFyZWFzLCBtb3N0IHpvbmVzIGFyZSB0aGUgb25seSB6b25lIGluIHRoZWlyIGNpdHknCiwgJ0RlY2VudCByeCBzdXBwbHksIGxvdyBudW1iZXIgb2YgTSsgcmVzdGF1cmFudHMnCiwgJ0dvb2QgcHJvcG9ydGlvbiBvZiBHNiBhbmQgRW50ZXJwcmlzZSByZXN0YXVyYW50cycKLCAnUmVsYXRpdmVseSBoaWdoIGRlbnNpdHkgb2YgcmVzdGF1cmFudHMsIGdvb2QgY3Vpc2luZSBkaXZlcnNpdHknCiwgJ0p1c3RlYXQgYW5kIERlbGl2ZXJvbyBjb21wZXRpbmcsIFViZXJlYXRzIHByZXNlbnQgYnV0IGhhcyBsb3dlciByeCBzdXBwbHknCiwgJ0V4YW1wbGUgem9uZXM6ICBBYmVyZGVlbiwgQ2hlbHRlbmhhbSwgU2xvdWdoLCBMYW5jYXN0ZXIsIEV4ZXRlciwgV2Fyd2ljaywgRHVyaGFtJwosICcnKQoKCmNsdXM1IDwtIGMoJ05pY2tuYW1lOiBPdXRlciBDaXR5JwosICdMYXJnZSB6b25lIHBvcHVsYXRpb24nCiwgJ1pvbmUgaXMgMiAtIDMgeWVhcnMgb2xkJwosICdWZXJ5IGxhcmdlIG1ldHJvcG9saXRhbiBhcmVhcyAtIG1vc3Qgem9uZXMgYXJlIG91dGVyIEJpcm1pbmdoYW0gYW5kIE1hbmNoZXN0ZXInCiwgJ0RlY2VudCByeCBzdXBwbHksIGRlY2VudCBjdWlzaW5lIGRpdmVyc2l0eScKLCAnSnVzdGVhdCBkb21pbmF0ZXMgaW4gdGVybXMgb2YgcmVzdGF1cmFudCBzdXBwbHknCiwgJzM3JSAoc2Vjb25kIGhpZ2hlc3QpIE0rIHJlc3RhdXJhbnRzJwosICdFeGFtcGxlIHpvbmVzOiBXYWxzYWxsLCAgQm9sdG9uLCBXb2x2ZXJoYW1wdG9uLCBEdWRsZXksIEVkZ2Jhc3RvbicKLCAnJwosICcnKQoKCmNsdXM0IDwtIGMoJ05pY2tuYW1lOiBNb21lbnR1bSAtIE5ldyBMYXVuY2ggYW5kIFJlbGF1bmNoZWQnCiwgJ01lZGl1bSB6b25lIHBvcHVsYXRpb24gYW5kIGxvdyBwb3B1bGF0aW9uIGRlbnNpdHknCiwgJ01vc3Qgem9uZXMgPCAxIHllYXIgb2xkLCBzb21lIG9sZGVyIHpvbmVzJwosICdNYWlubHkgbmV3IGxhdW5jaCBvciByZWxhdW5jaGVkIChlZyBMaXZpbmdzdG9uKSB6b25lcywgaGFuZGZ1bCBvZiBvbGRlciB6b25lcyAoQmFuZ29yLCBCZWVzdG9uLCBCZWRmb3JkKScKLCAnTG93IHJ4IHN1cHBseSwgbG93IGN1aXNpbmUgZGl2ZXJzaXR5JwosICdKdXN0ZWF0IGRvaW5nIGJldHRlciB0aGFuIERlbGl2ZXJvbyBhbmQgVWJlciAtIFViZXIgbm90IGluIDI1JSBvZiB6b25lcy4nCiwgJ0hpZ2hlc3QgcHJvcG9ydGlvbiBvZiBHNiByZXN0YXVyYW50cywgdmVyeSBoaWdoIE0rIHJlc3RhdXJhbnRzJwosICdNYWlubHkgY2FyIGRlbGl2ZXJpZXMnCiwgJ0V4YW1wbGUgem9uZXM6ICBEb25jYXN0ZXIsIFNjdW50aG9ycGUsIFdleW1vdXRoLCBTdW5kZXJsYW5kLCBCdXJ0b24gdXBvbiBUcmVudCwgRGVycnkgQ2l0eSBDZW50cmUnCiwgJycpCgoKY2x1czMgPC0gYygnTmlja25hbWU6IFdoeSBhcmUgd2UgaGVyZT8gYW5kIE0rIG9ubHkgem9uZXMnCiwgJ1JhbmdlIG9mIHpvbmUgcG9wdWxhdGlvbnMsIGFuZCBtZWRpdW0gcG9wdWxhdGlvbiBkZW5zaXR5JwosICdSYW5nZSBvZiBzbWFsbCB0byBsYXJnZSBtZXRyb3BvbGl0YW4gYXJlYXMnCiwgJ01vc3Qgem9uZXMgYXJlIDwgMiB5ZWFycyBvbGQnCiwgJ0xvd2VzdCByeCBzdXBwbHksIGxvd2VzdCBjdWlzaW5lIGRpdmVyc2l0eScKLCAnODQlIE0rIHJlc3RhdXJhbnRzJwosICdKdXN0ZWF0IGRvbWluYXRlcywgVWJlciBub3QgcHJlc2VudCBpbiAyMCUgb2YgdGhlIHpvbmVzJwosICdNYWluIGRpZmZlcmVuY2UgYmV0d2VlbiB0aGlzIGNsdXN0ZXIgYW5kIDw8IGlzIE0rIHJlc3RhdXJhbnRzLCBwb3B1bGF0aW9uIGRlbnNpdHksIGFuZCBqdXN0ZWF0IHJ4IHN1cHBseScKLCAnRXhhbXBsZSB6b25lczogT3V0ZXIgTmV3Y2FzdGxlLCBPdXRlciBSb3RoZXJoYW0sIE91dGVyIFNoZWZmaWVsZCwgTWFuY2hlc3RlciBFYXN0LCBTZWFoYW0nCiwgJycpCgoKCmNsdXNfZGVzYyA8LSBkYXRhLmZyYW1lKENsdXN0ZXIxID0gY2x1czEsCiAgICAgICAgICAgICAgICAgICBDbHVzdGVyMiA9IGNsdXMyLAogICAgICAgICAgICAgICAgICAgQ2x1c3RlcjMgPSBjbHVzMywKICAgICAgICAgICAgICAgICAgIENsdXN0ZXI0ID0gY2x1czQsCiAgICAgICAgICAgICAgICAgICBDbHVzdGVyNSA9IGNsdXM1KQoKY2x1c19kZXNjCgpgYGAKCgpIZXJlIGlzIGEgbW9yZSBkZXRhaWxlZCB2aWV3IG9mIHRoZSBmZWF0dXJlcyBpbiBlYWNoIHpvbmUuIFRha2UgYSBsb29rIGF0IHRoZSBiaXBsb3QgYWJvdmUgdG8gdW5kZXJzdGFuZCB3aGljaCBmZWF0dXJlcyBhcmUgbW9yZSBpbXBvcnRhbnQgaW4gZGlmZmVyZW50aWF0aW5nIHRoZW0uIFRoZSBtb3N0IGltcG9ydGFudCBmZWF0dXJlcyBhcmUgbnVtYmVyIG9mIGxpdmUgcmVzdGF1cmFudHMsIG51bWJlciBvZiBjdWlzaW5lcywgem9uZSB0ZW51cmUsIGNvbXBldGl0aXZlIHN1cHBseSBhbmQgcGVyY2VudGFnZSBvZiBNKyByZXN0YXVyYW50cy4gV2UndmUgYWxzbyBzdW1tYXJpc2VkIHNvbWUgZmVhdHVyZXMgdGhhdCB3ZXJlIG5vdCB1c2VkIGluIHRoZSBjbHVzdGVyaW5nLCBqdXN0IHRvIGlsbHVzdHJhdGUgdGhlIGRpZmZlcmVuY2UgaW4gdGhlIGNsdXN0ZXJzIG1vcmUgY2xlYXJseS4KYGBge3Iga21lYW5zXzN9CgpkZl9zdW1tIDwtIGRmX2ZpbmFsICU+JQogIGdyb3VwX2J5KGNsdXN0ZXIpICU+JQogIHN1bW1hcmlzZShudW1iZXJfb2Zfem9uZXMgPSBuKCksCiAgICAgICAgICAgIHpvbmVfdGVudXJlX2RheXMgPSByb3VuZChtZWFuKHpvbmVfdGVudXJlX2RheXMpKSwKICAgICAgICAgICAgem9uZV9wb3B1bGF0aW9uID0gcm91bmQobWVhbih6b25lX3BvcHVsYXRpb24pKSwKICAgICAgICAgICAgI2FyZWEgPSBtZWFuKGFyZWEpLAogICAgICAgICAgICBwb3BuX2RlbnNpdHkgPSByb3VuZChtZWFuKHBvcG5fZGVuc2l0eSkpLAogICAgICAgICAgICB6b25lc19pbl9jaXR5ID0gcm91bmQobWVhbih6b25lc19pbl9jaXR5KSksCiAgICAgICAgICAgIGxpdmVfcnhfdG90YWwgPSByb3VuZChtZWFuKGxpdmVfcnhfdG90YWwpKSwKICAgICAgICAgICAgbnVtYmVyX2N1aXNpbmVzID0gbWVhbihudW1iZXJfY3Vpc2luZXMpLAogICAgICAgICAgICAjcnhfcGVyX3Rob3VzYW5kX3BvcG4gPSBtZWFuKHJ4X3Blcl90aG91c2FuZF9wb3BuKSwKICAgICAgICAgICAgaGFzX2VkaXRpb25zX3NpdGUgPSBtZWFuKGhhc19lZGl0aW9uc19zaXRlKSwKICAgICAgICAgICAgcGN0X21wbHVzID0gbWVhbihwY3RfbXBsdXMpLAogICAgICAgICAgICBhdmdfcHJpY2VfY2F0ZWdvcnkgPSBtZWFuKGF2Z19wcmljZV9jYXRlZ29yeSksCiAgICAgICAgICAgIGNhcl9kZWxpdl9wY3QgPSBtZWFuKGNhcl9kZWxpdl9wY3QpLAoKICAgICAgICAgICAgY29tcF9qdXN0ZWF0X2Jvb2wgPSBtZWFuKGNvbXBfanVzdGVhdF9ib29sKSwKICAgICAgICAgICAgY29tcF91YmVyX2Jvb2wgPSBtZWFuKGNvbXBfdWJlcl9ib29sKSwKICAgICAgICAgICAgY29tcF9qdXN0ZWF0X3J4X3N1cHBseSA9IHJvdW5kKG1lYW4oY29tcF9qdXN0ZWF0X3J4X3N1cHBseSkpLAogICAgICAgICAgICBjb21wX3ViZXJfcnhfc3VwcGx5ID0gcm91bmQobWVhbihjb21wX3ViZXJfcnhfc3VwcGx5KSksCiAgICAgICAgICAgICMgbnVtX3J4X2c2ID0gbWVhbihudW1fcnhfZzYpLAogICAgICAgICAgICAjIG51bV9yeF9lbnRlcnByaXNlID0gbWVhbihudW1fcnhfZW50ZXJwcmlzZSksCiAgICAgICAgICAgICMgbnVtX3J4X2xvY2FsID0gbWVhbihudW1fcnhfbG9jYWwpLAogICAgICAgICAgICBudW1fcnhfZzI1X3BjdCA9IG1lYW4obnVtX3J4X2cyNV9wY3QpLAogICAgICAgICAgICBudW1fcnhfZW50ZXJwcmlzZV9wY3QgPSBtZWFuKG51bV9yeF9lbnRlcnByaXNlX3BjdCksCiAgICAgICAgICAgICNudW1fcnhfbG9jYWxfcGN0ID0gbWVhbihudW1fcnhfbG9jYWxfcGN0KSwKICAgICAgICAgICAgI251bV9yeF9ub3NlZ21lbnRfcGN0ID0gbWVhbihudW1fcnhfbm9zZWdtZW50X3BjdCksCiAgICAgICAgICAgICMgYW9mID0gbWVhbihhb2YpLAogICAgICAgICAgICAjIGFvdiA9IG1lYW4oYW92KSwKICAgICAgICAgICAgI3BsdXNfb3JkZXJzX3BjdCA9IG1lYW4ocGx1c19vcmRlcnNfcGN0KSwKICAgICAgICAgICAgIyBjb252ZW5pZW5jZV9vcmRlcnNfcGN0ID0gbWVhbihjb252ZW5pZW5jZV9vcmRlcnNfcGN0KSwKICAgIAogICAgICAgICAgICAjIGJpY3ljbGVfZGVsaXZfcGN0ID0gbWVhbiAoYmljeWNsZV9kZWxpdl9wY3QpLAogICAgICAgICAgICAjIHNjb290ZXJfZGVsaXZfcGN0ID0gbWVhbihzY29vdGVyX2RlbGl2X3BjdCksCiAgICAgICAgICAgICMgbnVsbF9kZWxpdl9wY3QgPSBtZWFuKG51bGxfZGVsaXZfcGN0KSwKICAgICAgICAgICAgIyBudW1fYWxjb2hvbF9yeF9wY3QgICA9IG1lYW4obnVtX2FsY29ob2xfcnhfcGN0KSwKICAgICAgICAgICAgIyBudW1fYW1lcmljYW5fcnhfcGN0ID0gbWVhbihudW1fYW1lcmljYW5fcnhfcGN0KSwKICAgICAgICAgICAgIyBudW1fYnJpdGlzaF9yeF9wY3QgPSBtZWFuKG51bV9icml0aXNoX3J4X3BjdCApLAogICAgICAgICAgICAjIG51bV9jaGluZXNlX3J4X3BjdCA9IG1lYW4obnVtX2NoaW5lc2VfcnhfcGN0KSwgCiAgICAgICAgICAgICMgbnVtX2Rlc3NlcnRfcnhfcGN0ID0gbWVhbihudW1fZGVzc2VydF9yeF9wY3QpLAogICAgICAgICAgICAjIG51bV9mcmVuY2hfcnhfcGN0ID0gbWVhbihudW1fZnJlbmNoX3J4X3BjdCksCiAgICAgICAgICAgICMgbnVtX2dyZWVrX3J4X3BjdCA9IG1lYW4obnVtX2dyZWVrX3J4X3BjdCksCiAgICAgICAgICAgICMgbnVtX2luZGlhbl9yeF9wY3QgID0gbWVhbihudW1faW5kaWFuX3J4X3BjdCApLAogICAgICAgICAgICAjIG51bV9pdGFsaWFuX3J4X3BjdCA9IG1lYW4obnVtX2l0YWxpYW5fcnhfcGN0KSwKICAgICAgICAgICAgIyBudW1famFwYW5lc2VfcnhfcGN0ID0gbWVhbihudW1famFwYW5lc2VfcnhfcGN0KSwgCiAgICAgICAgICAgICMgbnVtX2tvcmVhbl9yeF9wY3QgID0gbWVhbihudW1fa29yZWFuX3J4X3BjdCApLAogICAgICAgICAgICAjIG51bV9sZWJhbmVzZV9yeF9wY3QgID0gbWVhbihudW1fbGViYW5lc2VfcnhfcGN0KSwgCiAgICAgICAgICAgICMgbnVtX21leGljYW5fcnhfcGN0ID0gbWVhbihudW1fbWV4aWNhbl9yeF9wY3QgKSwKICAgICAgICAgICAgIyBudW1fc3BhbmlzaF9yeF9wY3QgPSBtZWFuKG51bV9zcGFuaXNoX3J4X3BjdCksCiAgICAgICAgICAgICMgbnVtX3RoYWlfcnhfcGN0ID0gbWVhbihudW1fdGhhaV9yeF9wY3QpLAogICAgICAgICAgICAjIG51bV90dXJraXNoX3J4X3BjdCA9IG1lYW4obnVtX3R1cmtpc2hfcnhfcGN0KSwKICAgICAgICAgICAgIyBudW1fdmlldG5hbWVzZV9yeF9wY3QgPSBtZWFuKG51bV92aWV0bmFtZXNlX3J4X3BjdCksCiAgICAgICAgICAgICMgbnVtX21pc3NpbmdfcnhfcGN0ID0gbWVhbihudW1fbWlzc2luZ19yeF9wY3QpCiAgICAgICAgICAgICkKICAKbnVtcyA8LSB2YXBwbHkoZGZfc3VtbSwgaXMubnVtZXJpYywgRlVOLlZBTFVFID0gbG9naWNhbCgxKSkKZGZfc3VtbVssbnVtc10gPC0gcm91bmQoZGZfc3VtbVssbnVtc10sIGRpZ2l0cyA9IDIpCgpkZl9jb21wYXJlIDwtIGFzLmRhdGEuZnJhbWUodChkZl9zdW1tKSkKbmFtZXMoZGZfY29tcGFyZSkgPC0gYygnQ2x1c3RlciAxJywgJ0NsdXN0ZXIgMicsICdDbHVzdGVyIDMnLCAnQ2x1c3RlciA0JywgJ0NsdXN0ZXIgNScpCmRmX2NvbXBhcmUgPC0gZGZfY29tcGFyZVstMSxdCgpkZl9jb21wYXJlCgpgYGAKCmBgYCB7ciBjbHVzdF9jb21wYXJpc29uc30KIyAKIyBjbHVzdGVyX2RmIDwtIGRvLmNhbGwoY2JpbmQsIGNsdXN0ZXJfbGlzdCkKIyAKIyBjbHVzdGVyX2RmIDwtIGNsdXN0ZXJfZGZbLGMoMSwyLDYpXQojIG5hbWVzKGNsdXN0ZXJfZGYpWzFdIDwtICJ6b25lX2NvZGUiCiMgY2x1c3Rlcl9kZiR6b25lX2NoYW5nZSA8LSB3aXRoKGNsdXN0ZXJfZGYsIGlmZWxzZShjdXN0XzUuY2x1c3RlciA9PSBub2N1c3RfNS5jbHVzdGVyLCAiTiIsICJZIikpCiMgCiMgY2x1c3Rlcl9kZl9ZIDwtIGNsdXN0ZXJfZGZbY2x1c3Rlcl9kZiR6b25lX2NoYW5nZSA9PSAnWScsXQojIAojIAojIGNoYW5nZTEgPC0gY2x1c3Rlcl9kZl9ZJHpvbmVfY29kZVtjbHVzdGVyX2RmX1kkY3VzdF81LmNsdXN0ZXIgPT0gMyAmIGNsdXN0ZXJfZGZfWSRub2N1c3RfNS5jbHVzdGVyID09IDRdCiMgY2hhbmdlMiA8LSBjbHVzdGVyX2RmX1kkem9uZV9jb2RlW2NsdXN0ZXJfZGZfWSRjdXN0XzUuY2x1c3RlciA9PSA0ICYgY2x1c3Rlcl9kZl9ZJG5vY3VzdF81LmNsdXN0ZXIgPT0gM10KIyBjaGFuZ2UzIDwtIGNsdXN0ZXJfZGZfWSR6b25lX2NvZGVbY2x1c3Rlcl9kZl9ZJGN1c3RfNS5jbHVzdGVyID09IDQgJiBjbHVzdGVyX2RmX1kkbm9jdXN0XzUuY2x1c3RlciA9PSAxXQojIGNoYW5nZTQgPC0gY2x1c3Rlcl9kZl9ZJHpvbmVfY29kZVtjbHVzdGVyX2RmX1kkY3VzdF81LmNsdXN0ZXIgPT0gMyAmIGNsdXN0ZXJfZGZfWSRub2N1c3RfNS5jbHVzdGVyID09IDVdCgoKYGBgCgpUaGVyZSB3YXMgc29tZSBkZWJhdGUgYXJvdW5kIHdoZXRoZXIgY3VzdG9tZXItcmVsYXRlZCBmYWN0b3JzIGxpa2UgQU9GIGFuZCBudW1iZXIgb2YgbmV3IGN1c3RvbWVycyBzaG91bGQgYmUgaW5jbHVkZWQgaW4gdGhlIGNsdXN0ZXJpbmcgYWxnb3JpdGhtLiBXZSByZW1vdmVkIGN1c3RvbWVyLXJlbGF0ZWQgZmFjdG9ycyB0byBzZWUgaG93IHRoZSBjbHVzdGVycyBjaGFuZ2VkLiBPbmx5ICoqNy42OSUqKiBvZiBjbHVzdGVycyBjaGFuZ2VkLgoKPGJyPjxicj4KCjxhIG5hbWU9ImJvb3QxX2FuY2hvciI+PC9hPiAKCiMjIyBCb290c3RyYXBwaW5nIEttZWFucwoKV2UgbmVlZCB0byB1bmRlcnN0YW5kIGhvdyB3ZWxsIG91ciBjbHVzdGVyaW5nIGFsZ29yaXRobSBoYXMgcGVyZm9ybWVkIC0gZG8gb3VyIDUgY2x1c3RlcnMgcmVwcmVzZW50IGFjdHVhbCBzdHJ1Y3R1cmUgaW4gdGhlIGRhdGEsIG9yIGFyZSB0aGV5IHByb2R1Y3RzIG9mIG91ciBhbGdvcml0aG0/IEttZWFucyBpcyBhbiB1bnN1cGVydmlzZWQgbWFjaGluZSBsZWFybmluZyBhbGdvcml0aG0sIHNvIHRoZXJlIGlzIG5vICJyaWdodCIgYW5zd2VyIHRvIGNvbXBhcmUgb3VyIG91dGNvbWVzIHRvLiBGdXJ0aGVybW9yZSwgdGhlIGNsdXN0ZXJzIHNwZWNpZmllZCBjYW4gdmFyeSBkZXBlbmRpbmcgb24gd2hlcmUgdGhlIGluaXRpYWwgY2VudHJvaWQgKGNlbnRyZSBvZiB0aGUgY2x1c3RlcikgaXMgc2V0LgoKV2UgY2hlY2sgd2hldGhlciBvdXIgY2x1c3RlcnMgcmVwcmVzZW50IGEgdHJ1ZSBzdHJ1Y3R1cmUgYnkgc2VlaW5nIGlmIHRoZXkgcmVtYWluIHN0YWJsZSB1bmRlciBwbGF1c2libGUgdmFyaWF0aW9ucyBpbiB0aGUgZGF0YXNldC4gV2UgY2hlY2sgdGhlIHN0YWJpbGl0eSBvZiBvdXIgY2x1c3RlcnMgdGhlIGZvbGxvd2luZyB3YXk6CgoxLiAqKkJvb3RzdHJhcCoqIGEgc2FtcGxlIG9mIG91ciBmZWF0dXJlIGRhdGFzZXQKICAgICsgU2FtcGxlIHdpdGggcmVwbGFjZW1lbnQgICAgICAKPGJyPgoyLiBJbXBsZW1lbnQgdGhlICoqay1tZWFucyoqIGFsZ29yaXRobSBvbiB0aGUgYm9vdHN0cmFwcGVkIGRhdGEgIAoKMy4gQ29tcGFyZSB0aGUgKipzaW1pbGFyaXR5Kiogb2YgdGhlIGNsdXN0ZXJzIGluICgyKSB0byBvdXIgaW5pdGlhbCBjbHVzdGVyaW5nIG91dHB1dCAgCiAgICArIExvb2sgYXQgcGFpcndpc2UgY29tYmluYXRpb25zIG9mIHpvbmVzLCBhbmQgY29tcHV0ZSBob3cgbWFueSByZW1haW4gaW4gdGhlIHNhbWUgY2x1c3RlciAgICAgICAKPGJyPgo0LiBJdGVyYXRlIG92ZXIgc3RlcHMgMSAtIDMsIDUwMCB0aW1lcyB0byBzZWUgdGhlICoqY3VtdWxhdGl2ZSBwcm9wb3J0aW9uIG9mIHpvbmUgcGFpcnMgdGhhdCBkaXNzb2x2ZSoqIG92ZXIgNTAwIGl0ZXJhdGlvbnMKCgpGb3IgZXhhbXBsZSwgaW1hZ2luZSB0aGF0IG91ciBvcmlnaW5hbCBjbHVzdGVyaW5nIHJlc3VsdHMgaW4gYSBjbHVzdGVyOiAgICAgCiAgICAgICAgICAgICAgJEEgPSBce0VETiwgQ0FNLCBDSEUsIFRPTlx9JCAgICAgIAogICAgICAgICAgICAgIApXZSBvYnRhaW4gMTAgcG9zc2libGUgcGFpcndpc2UgY29tYmluYXRpb25zOiAkRUROLUVETiwgRUROLUNBTSwgRUROLUNIRSwgRUROLVRPTiwgQ0FNLUNBTSQgJENBTS1DSEUsIENBTS1UT04sIENIRS1DSEUsIENIRS1UT04sIFRPTi1UT04kICAgICAgICAKTm90ZSB0aGF0IHdlIGFjY291bnQgZm9yIHJlcGVhdHMgb2YgdGhlIHNhbWUgem9uZSwgYW5kIHRoZXNlIGNvbWJpbmF0aW9ucyBtaWdodCBvY2N1cmUgaW4gdGhlIGJvb3RzdHJhcHBlZCBkYXRhLgoKCk91ciBmaXJzdCBib290c3RyYXBwZWQgaW1wbGVtZW50YXRpb24gdGhyb3dzIHVwIGEgY2x1c3RlcjogICAgIAogICAgICAgICAgICAgICRCID1ce0NBTSwgQ0FNLCBDSEUsIFdDVlx9JCAgICAKICAgICAgICAgICAgICAKTm90ZSB0aGF0IHNvbWUgcmVwZXRpdGlvbiBvY2N1cnMgZHVlIHRvIHVzIHNhbXBsaW5nIHdpdGggcmVwbGFjZW1lbnQgKENBTSksIHdoaWNoIGFsc28gY2F1c2VzIEVETiBhbmQgVE9OIHRvIG5vdCBiZSBwaWNrZWQgaW4gdGhlIHJhbmRvbSBzYW1wbGUuIFRoaXMgY2x1c3RlciBhbHNvIGhhcyBhIG5ldyB6b25lLCBXQ1YsIHdoaWNoIHdhcyBub3QgYXNzaWduZWQgdG8gdGhhdCBjbHVzdGVyIG9yaWdpbmFsbHkuICAgICAgCldlIG9idGFpbiA0IHBvc3NpYmxlIHBhaXJ3aXNlIGNvbWJpbmF0aW9uczogJENBTS1DQU0sIENBTS1DSEUsIENBTS1XQ1YsIENIRS1XQ1YkLiAyIG9mIHRoZXNlIDQgcGFpcnMgb2NjdXIgaW4gb3VyIG9yaWdpbmFsIGNsdXN0ZXIsIHNvIHdlIGNhbGN1bGF0ZSBvdXIgc2ltaWxhcml0eSBpbmRleCBhcyAkU2ltaWxhcml0eSA9IDIvNCA9IDAuNSQgCgoKV2UgaW5pdGlhbGx5IHVzZWQgdGhlICoqSmFjY2FyZCBpbmRleCoqIHRvIGV2YWx1YXRlIHRoZSBzdGFiaWxpdHkgb2Ygb3VyIGNsdXN0ZXJzLCBhcyB0aGF0IGlzIHRoZSBtb3N0IHBvcHVsYXIgbWV0aG9kLiBIb3dldmVyLCB3ZSB1bHRpbWF0ZWx5IHJlYWxpc2VkIHRoYXQgdGhpcyB3YXMgbm90IGFwcGxpY2FibGUgdG8gb3VyIGJ1c2luZXNzIHByb2JsZW0uCgpJbiB0aGUgZXhhbXBsZSBhYm92ZSwgd2Ugd291bGQgY2FsY3VsYXRlIHRoZSBKYWNjYXJkIGluZGV4IGFzICRKKEEsQikgPSBce0Eg4oipIEJcfS9ce0EgVSBCXH0gPSAgXHtDQU0sQ0hFXH0gLyBce0VETiwgQ0FNLCBDSEUsIFRPTiwgV0NWXH0gPSAwLjQkIAoKVGhpcyB3b3VsZCByZXN1bHQgaW4gYSBsb3cgSmFjY2FyZCBJbmRleCwgc2ltcGx5IGJlY2F1c2Ugem9uZXMgRUROIGFuZCBUT04gd2VyZSBub3Qgc2FtcGxlZCBpbiB0aGUgYm9vdHN0cmFwcGVkIGRhdGEuIFNhbXBsaW5nIHdpdGggcmVwbGFjZW1lbnQgbWVhbnQgdGhhdCBvdXIgYm9vdHN0cmFwcGVkIGNsdXN0ZXJzIHdvdWxkIGFsbW9zdCBhbHdheXMgaGF2ZSBsZXNzIHpvbmVzIHRoYW4gb3VyIG9yaWdpbmFsIGNsdXN0ZXJzLiBTbywgdGhlIEphY2NhcmQgaW5kZXggd291bGQgdW5mYWlybHkgcGVuYWxpemUgdXMgZm9yIHRoYXQuCgoKYGBgIHtyIGJvb3RzdHJhcHBpbmdfMSwgbWVzc2FnZSA9IEYsIGV2YWwgPSBGfQoKc3RhcnQgPC0gU3lzLnRpbWUoKQoKY2x1c3Rlcl9saXN0IDwtIGxpc3QoKQpjbHVzdGVyX2RmX2xpc3QgPC0gbGlzdCgpCnBhaXJ3aXNlX2xpc3RfYm9vdCA8LSBsaXN0KCkKCgpzYW1wbGVfc2l6ZSA8LSBucm93KGRmX3ZhcnMyKQpuX2l0ZXIgPC0gMToyMDAKbl9jbHVzdCA8LSAxOmsKICAKZm9yIChpIGluIG5faXRlcikgewoKICByb3dzX3RvX3NhbXBsZSA8LSBzYW1wbGUoYygxOnNhbXBsZV9zaXplKSwgcmVwbGFjZSA9IFQpCiAgZGZfYm9vdCA8LSBkZl92YXJzMltyb3dzX3RvX3NhbXBsZSxdCiAgCiAgI2RmX2Jvb3QgPC0gZGZfdmFyczIKICAKICAgICAgaWYoaSA9PSAxKSB7CiAgICAgICAgb3Aua21lYW5zLmJvb3QgIDwtIG9wLmttZWFucyAjb3JpZ2luYWwgY2x1c3RlcnMKICAgICAgfSBlbHNlIHsKICAgICAgc2V0LnNlZWQoc2FtcGxlKGMoMToxMDAwKSwxKSkKICAgICAgb3Aua21lYW5zLmJvb3QgPC0ga21lYW5zKGRmX2Jvb3RbLDI6bmNvbChkZl9ib290KV0sIGssIGl0ZXIubWF4ID0gMTApCiAgICAgIH0KICAKICAKICB6b25lX2NvZGVfbmFtZXMgPC0gcm93bmFtZXMoYXMuZGF0YS5mcmFtZShvcC5rbWVhbnMuYm9vdCRjbHVzdGVyKSkKICB6b25lX2NvZGVfbmFtZXMgPC0gc3ViKCIuWzEtOV0rIiwgIiIsIHpvbmVfY29kZV9uYW1lcykgI3RvIHByZXZlbnQgcmUtbmFtaW5nIG9mIHJlcGVhdGVkIHpvbmVzCiAgCiAgY2x1c3RlcnMgPC0gZGF0YS5mcmFtZSh6b25lX2NvZGUgPSB6b25lX2NvZGVfbmFtZXMsCiAgICAgICAgICAgICAgICAgICAgICAgICBjbHVzdGVyID0gb3Aua21lYW5zLmJvb3QkY2x1c3RlciwKICAgICAgICAgICAgICAgICAgICAgICAgIGl0ZXJhdGlvbiA9IGkpCiAgY2x1c3RlcnMkem9uZV9jb2RlIDwtIGFzLmNoYXJhY3RlcihjbHVzdGVycyR6b25lX2NvZGUpCiAgCiAgcGFpcndpc2VfbGlzdF9ib290LnN1YiA8LSBsaXN0KCkKICAKICBmb3IgKG0gaW4gbl9jbHVzdCkgewogICAgc3ViLmNsdXN0IDwtIGNsdXN0ZXJzJHpvbmVfY29kZVtjbHVzdGVycyRjbHVzdGVyID09IG1dCiAgICBmb3IgKG4gaW4gMTpsZW5ndGgoc3ViLmNsdXN0KSkgewogICAgICBmb3IgKHAgaW4gMTpsZW5ndGgoc3ViLmNsdXN0KSkgewogICAgICBwYWlyIDwtIHBhc3RlKHNvcnQoYyhzdWIuY2x1c3Rbbl0sIHN1Yi5jbHVzdFtwXSkpLCBjb2xsYXBzZSA9ICItIikKICAgICAgcGFpcndpc2VfbGlzdF9ib290LnN1YltbcGFpcl1dIDwtIHBhaXIKICAgIH0KICAgIH0KCiAgfQoKICAgICAgIyBmb3IgKG0gaW4gMTpucm93KGNsdXN0ZXJzKSkgewogICAgICAjICAgZm9yKCBuIGluIDE6bnJvdyhjbHVzdGVycykpIHsKICAgICAgIyAgICAgaWYgKGNsdXN0ZXJzW20sMl0gPT0gY2x1c3RlcnNbbiwyXSkgewogICAgICAjICAgICAgIHBhaXIgPC0gcGFzdGUoc29ydChjKGNsdXN0ZXJzW20sMV0sIGNsdXN0ZXJzW24sMV0pKSwgY29sbGFwc2UgPSAiLSIpCiAgICAgICMgICAgICAgcGFpcndpc2VfbGlzdF9ib290LnN1YltbcGFpcl1dIDwtIHBhaXIKICAgICAgIyAgICAgfQogICAgICAjICAgfQogICAgICAjIH0KICAKICBwYWlyd2lzZV9saXN0X2Jvb3RbW2xlbmd0aChwYWlyd2lzZV9saXN0X2Jvb3QpKyAxXV0gPC0gcGFpcndpc2VfbGlzdF9ib290LnN1YgogIAogIGZvciAobiBpbiBuX2NsdXN0KSB7CiAgICBjbHVzdGVyX2xpc3RbW3Bhc3RlMChpLCAiLSIsIG4pXV0gPC0gYXMudmVjdG9yKGNsdXN0ZXJzJHpvbmVfY29kZVtjbHVzdGVycyRjbHVzdGVyID09IG5dKQogIH0KICBjbHVzdGVyX2RmX2xpc3RbW2ldXSA8LSBjbHVzdGVycwoKICBwYXN0ZTAoIkNvbXBsZXRlZDogSXRlcmF0aW9uICIsaSkKfQoKZW5kIDwtIFN5cy50aW1lKCkKICAKdGltZS50YWtlbiA8LSBlbmQgLSBzdGFydCAgCiAgCgpkZl9maW5hbF9ib290IDwtIGRvLmNhbGwocmJpbmQsIGNsdXN0ZXJfZGZfbGlzdCkKCm51bWVyX2xpc3QgPC0gbGlzdCgpCmRlbm9tX2xpc3QgPC0gbGlzdCgpCgpmb3IgKGkgaW4gMjpsZW5ndGgocGFpcndpc2VfbGlzdF9ib290KSkgewogIG9yaWcgPC0gcGFpcndpc2VfbGlzdF9ib290W1sxXV0KICB0ZXN0IDwtIHBhaXJ3aXNlX2xpc3RfYm9vdFtbaV1dCiAgbnVtZXIgPC0gc3VtKHRlc3QgJWluJSBvcmlnKQogIGRlbm9tIDwtIGxlbmd0aCh0ZXN0KQogIG51bWVyX2xpc3QgW1tsZW5ndGgobnVtZXJfbGlzdCApKzFdXSA8LSBudW1lcgogIGRlbm9tX2xpc3QgW1tsZW5ndGgoZGVub21fbGlzdCApKzFdXSA8LSBkZW5vbQogIH0KCm51bWVyX2RmIDwtIHQoZGF0YS5mcmFtZShudW1lcl9saXN0KSkKZGVub21fZGYgPC0gdChkYXRhLmZyYW1lKGRlbm9tX2xpc3QpKQppdGVyX2RmIDwtIGRhdGEuZnJhbWUodCA9IGMoMTpsZW5ndGgobnVtZXJfZGYpKSwKICAgICAgICAgICAgICAgICAgICAgIG9yaWdfcGFpcnMgPSBudW1lcl9kZiwKICAgICAgICAgICAgICAgICAgICAgIHRvdGFsX3BhaXJzID0gZGVub21fZGYpCgppdGVyX2RmJGN1bXVfb3JpZ19wYWlycyA8LSBjdW1zdW0oaXRlcl9kZiRvcmlnX3BhaXJzKQppdGVyX2RmJGN1bXVfdG90YWxfcGFpcnMgPC0gY3Vtc3VtKGl0ZXJfZGYkdG90YWxfcGFpcnMpCml0ZXJfZGYkbWlzbWF0Y2ggPC0gd2l0aChpdGVyX2RmLCAxLSAoY3VtdV9vcmlnX3BhaXJzL2N1bXVfdG90YWxfcGFpcnMpKQoKaXRlcl9kZl9rbWVhbnMgPC0gaXRlcl9kZgoKYGBgCgpXZSBzZWUgdGhhdCB0aGUgc3RhYmlsaXR5IG9mIG91ciBjbHVzdGVycyBpcyBxdWl0ZSBoaWdoLiBPdmVyIHRpbWUsIGFyb3VuZCAqKmByIHJvdW5kKGl0ZXJfZGZfa21lYW5zJG1pc21hdGNoW25yb3coaXRlcl9kZl9rbWVhbnMpXSAqMTAwLDIpYCUqKiBvZiBvdXIgem9uZS1wYWlycyBlbmQgdXAgc2VnbWVudGVkIGludG8gZGlmZmVyZW50IGNsdXN0ZXJzLiBUaGlzIG1lYW5zIHRoYXQgKipgciAxMDAtIHJvdW5kKGl0ZXJfZGZfa21lYW5zJG1pc21hdGNoW25yb3coaXRlcl9kZl9rbWVhbnMpXSAqMTAwLDIpIGAlKiogb2Ygb3VyIHpvbmUtcGFpcnMgcmVtYWluIGluIHRoZSBzYW1lIGNsdXN0ZXIuIFRoaXMgaXMgZ3JlYXQuCmBgYCB7ciBib290c3RyYXBwaW5nXzJ9CnBsb3RfbHkoIHggPSBpdGVyX2RmX2ttZWFucyR0LCB5ID0gaXRlcl9kZl9rbWVhbnMkbWlzbWF0Y2gsIG1vZGUgPSAnbGluZXMnLCB0eXBlID0gJ3NjYXR0ZXInKSAlPiUKICBsYXlvdXQodGl0bGUgPSAiJSBvZiBpbmNvcnJlY3QgcGFpcndpc2UgbWF0Y2hlcyBvdmVyIHRpbWUiLAogICAgICAgICB4YXhpcyA9IGxpc3QodGl0bGUgPSAiTnVtYmVyIG9mIEl0ZXJhdGlvbnMiKSwgCiAgICAgICAgIHlheGlzID0gbGlzdCh0aXRsZSA9ICIlIEluY29ycmVjdCBNYXRjaGVzIiwgcmFuZ2UgPSBjKDAsMC40KSkpCgojIHBsb3RfbHkoIHkgPSBpdGVyX2NvbXAkcGN0X2Rpc3NvbHZlZCwgbW9kZSA9ICdsaW5lcycsIHR5cGUgPSAnc2NhdHRlcicpICU+JQojICAgbGF5b3V0KHRpdGxlID0gIiUgb2YgZGlzc29sdmVkIGNsdXN0ZXJzIG92ZXIgdGltZSIsCiMgICAgICAgICAgeGF4aXMgPSBsaXN0KHRpdGxlID0gIk51bWJlciBvZiBJdGVyYXRpb25zIiksCiMgICAgICAgICAgeWF4aXMgPSBsaXN0KHRpdGxlID0gIiUgRGlzc29sdmVkIENsdXN0ZXJzIikpCmBgYAoKPGEgbmFtZT0iZmlvcF9hbmNob3IiPjwvYT4gCgojIyMgRmluYWwgT3V0cHV0cwo8YnI+CgpgYGAge3IgdG9fc25vd2ZsYWtlLCBlY2hvID0gRiwgbWVzc2FnZSA9IEZ9CgpkZl9maW5hbCRjbHVzdGVyX25hbWUgPC0gZGZfZmluYWwkY2x1c3RlcgpkZl9maW5hbCRjbHVzdGVyX25hbWUgPC0gd2l0aChkZl9maW5hbCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShjbHVzdGVyID09IDEsICJVcmJhbiBXaW5uZXJzIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UoY2x1c3RlciA9PSAyICwgIk9wcG9ydHVuaXR5IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UoY2x1c3RlciA9PSAzLCAiV2h5IGFyZSB3ZSBoZXJlPyBNKyB6b25lcyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShjbHVzdGVyID09IDQsICJNb21lbnR1bSIsICJPdXRlciBDaXR5IikpKSkpCgpkZl9maW5hbCA8LSBkZl9maW5hbFssYygxLDIsMTA3LCAzOjEwNildCgpzbm93Zmxha2UuY29ubmVjdG9yOjp3cml0ZV90b19zbm93Zmxha2UoZGZfZmluYWwsc25vd2ZsYWtlX3RhYmxlX25hbWUgPSAnc2NyYXRjaC5hZ2dyZWdhdGUuem9uZV9zZWdtZW50YXRpb25fUlVLJywgb3ZlcndyaXRlID0gVCkKCmRmX2ZpbmFsW29yZGVyKGRmX2ZpbmFsJHpvbmVfbmFtZSksXQoKCgpgYGAKCjxhIG5hbWU9ImFwcGUxX2FuY2hvciI+PC9hPiAKCiMgQXBwZW5kaXgKPGJyPgoKCjxhIG5hbWU9ImNsdXMyX2FuY2hvciI+PC9hPiAKCiMjIyMgQ2x1c3RlcmluZyB1c2luZyBIaWVyYXJjaGljYWwgQ2x1c3RlcmluZwoKKipXZSB1bHRpbWF0ZWx5IGRlY2lkZWQgYWdhaW5zdCBoaWVyYXJjaGljYWwgY2x1c3RlcmluZyBmb3IgMiByZWFzb25zOioqCgoqIDEuIEhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nIGRvZXMgbm90IHdvcmsgd2VsbCB3aXRoIGxhcmdlIGRhdGFzZXRzCgoqIDIuIFRoZSBmaW5hbCBjbHVzdGVyIG91dHB1dHMgd2VyZSBub3QgYXMgaW50dWl0aXZlIGFzIHRoZSBrbWVhbnMgY2x1c3RlcnMKCjxicj4KCldlIGFsc28gdXNlIGhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nIHRvIHNlZ21lbnQgb3VyIHpvbmVzLCB0byBzZWUgaG93IHRoZSByZXN1bHRzIGNvbXBhcmUgdG8gdG8gS21lYW5zLiBXZSB1c2UgdGhlIHNhbWUgc2V0IG9mIGZlYXR1cmVzIHVzZWQgZm9yIEttZWFucyBjbHVzdGVyaW5nLCBhbmQgc3RhcnQgY2x1c3RlcmluZyB6b25lcyBiYXNlZCBvbiBob3cgc2ltaWxhciB0aGV5IGFyZSAoY2FsY3VsYXRlZCB1c2luZyBFdWNsaWRlYW4gZGlzdGFuY2UpLiBXZSB1c2UgTWF4aW11bS8gQ29tcGxldGUgbGlua2FnZSBjbHVzdGVyaW5nIGFzIHRoaXMgdGVuZHMgdG8gcHJvZHVjZSBtb3JlIGNvbXBhY3QgY2x1c3RlcnMuCgpgYGB7ciBoY2x1c18xLCBmaWcuaGVpZ2h0PTUsIGZpZy53aWR0aD0gMTAsIGZpZy5hbGlnbj1UfQoKc2V0LnNlZWQoOTI2KQoKZGZfdmFyczMgPC0gZGZfdmFyczIKZGYuZGlzdCA8LSBkaXN0KGRmX3ZhcnMzWywyOm5jb2woZGZfdmFyczMpXSwgbWV0aG9kID0gJ2V1Y2xpZGVhbicpCmhjIDwtIGhjbHVzdChkZi5kaXN0LCBtZXRob2QgPSAnd2FyZC5EMicpCgpoY2QgPC0gYXMuZGVuZHJvZ3JhbShoYykKCnBsb3QoaGMsIGxhYmVscyA9IGRmX3ZhcnMzJHpvbmVfY29kZSxoYW5nID0gLTEsIGNleCA9IDAuNCwgeGxhYiA9ICJab25lcyIpCgpvcCA9IHBhcihiZyA9ICIjZDdlMGRmIikKcGxvdChjdXQoaGNkLCBoID0gMS43NSkkdXBwZXIsIG1haW4gPSAidXBwZXIgdHJlZSIsCiAgICAgY29sLm1haW4gPSAiIzQ1QURBOCIsIGNvbC5sYWIgPSAiIzdDODA3MSIsIAogICAgIGNvbC5heGlzID0gIiNGMzg2MzAiLCBsd2QgPSAzLCBsdHkgPSAzLCBzdWIgPSAiIikKIyBhZGQgYXhpcwpheGlzKHNpZGUgPSAyLCBhdCA9IHNlcSgwLCA0MDAsIDEwMCksIGNvbCA9ICIjRjM4NjMwIiwgbGFiZWxzID0gRkFMU0UsCiAgICBsd2QgPSAyKQojIGFkZCB0ZXh0IGluIG1hcmdpbgptdGV4dChzZXEoMCwgNDAwLCAxMDApLCBzaWRlID0gMiwgYXQgPSBzZXEoMCwgNDAwLCAxMDApLCBsaW5lID0gMSwKICAgIGNvbCA9ICIjQTM4NjMwIiwgbGFzID0gMikKCgoKCmBgYApUaGUgZnVsbCBkZW5kcm9ncmFtIGlzIHRvbyBsYXJnZSBmb3IgZGlzY2VybmluZyBhbnl0aGluZyBtZWFuaW5nZnVsLCBzbyB3ZSBkaXNwbGF5IHRoZSB1cHBlciBwYXJ0IG9mIHRoZSB0cmVlLCB3aGVyZSBoID4gMi4gVGhlIG9wdGltYWwgY3V0b2ZmIGhlaWdodCBzZWVtcyB0byBiZSBhcm91bmQgNCAtIHRoaXMgZ2V0cyB1cyA1IGNsdXN0ZXJzLiBDdXR0aW5nIHRoZSBkZW5kcm9ncmFtIGFueSBoaWdoZXIgd291bGQgcmVzdWx0IGluIDMgY2x1c3RlcnMgKHRvbyBsaXR0bGUpLgoKKipOb3RlOioqIFdlIGRpZCBhdHRlbXB0IGttZWFucyB3aXRoIDYgY2x1c3RlcnMsIGJ1dCB0aGlzIGVuZGVkIHVwIHNwbGl0dGluZyBDbHVzdGVyIDEgKE1vbWVudHVtIC0gTmV3IExhdW5jaCBhbmQgUmVsYXVuY2hlZCkgaW50byAyIHNlZ21lbnRzLiBXZSBzZXR0bGVkIG9uIDUgY2x1c3RlcnMsIGFzIGl0IGRpZCBub3QgbWFrZSBidXNpbmVzcyBzZW5zZSB0byBoYXZlIDIgY2x1c3RlcnMgY2F0ZXJpbmcgdG8gbmV3IHpvbmVzLgoKYGBge3IgaGNsdXNfMiwgZmlnLmhlaWdodD01fQoKc3ViX2dycCA8LSBjdXRyZWUoaGMsIGsgPSA1KQoKZGZfZmluYWwyIDwtICBjYmluZChkZiwgY2x1c3RlciA9IHN1Yl9ncnApCmZ2aXpfY2x1c3RlcihsaXN0KGRhdGEgPSBkZl92YXJzM1ssMjpuY29sKGRmX3ZhcnMzKV0sIGNsdXN0ZXIgPSBzdWJfZ3JwKSwgbGFiZWxzaXplID0gMTApCgpgYGAKCmBgYCB7ciBoY2x1c18zfQoKZGZfc3VtbTIgPC0gZGZfZmluYWwyICU+JQogIGdyb3VwX2J5KGNsdXN0ZXIpICU+JQogIHN1bW1hcmlzZShudW1iZXJfb2Zfem9uZXMgPSBuKCksCiAgICAgICAgICAgIHpvbmVfdGVudXJlX2RheXMgPSByb3VuZChtZWFuKHpvbmVfdGVudXJlX2RheXMpKSwKICAgICAgICAgICAgem9uZV9wb3B1bGF0aW9uID0gcm91bmQobWVhbih6b25lX3BvcHVsYXRpb24pKSwKICAgICAgICAgICAgI2FyZWEgPSBtZWFuKGFyZWEpLAogICAgICAgICAgICBwb3BuX2RlbnNpdHkgPSByb3VuZChtZWFuKHBvcG5fZGVuc2l0eSkpLAogICAgICAgICAgICB6b25lc19pbl9jaXR5ID0gcm91bmQobWVhbih6b25lc19pbl9jaXR5KSksCiAgICAgICAgICAgIGxpdmVfcnhfdG90YWwgPSByb3VuZChtZWFuKGxpdmVfcnhfdG90YWwpKSwKICAgICAgICAgICAgbnVtYmVyX2N1aXNpbmVzID0gbWVhbihudW1iZXJfY3Vpc2luZXMpLAogICAgICAgICAgICAjcnhfcGVyX3Rob3VzYW5kX3BvcG4gPSBtZWFuKHJ4X3Blcl90aG91c2FuZF9wb3BuKSwKICAgICAgICAgICAgaGFzX2VkaXRpb25zX3NpdGUgPSBtZWFuKGhhc19lZGl0aW9uc19zaXRlKSwKICAgICAgICAgICAgcGN0X21wbHVzID0gbWVhbihwY3RfbXBsdXMpLAogICAgICAgICAgICBhdmdfcHJpY2VfY2F0ZWdvcnkgPSBtZWFuKGF2Z19wcmljZV9jYXRlZ29yeSksCiAgICAgICAgICAgIGNhcl9kZWxpdl9wY3QgPSBtZWFuKGNhcl9kZWxpdl9wY3QpLAoKICAgICAgICAgICAgY29tcF9qdXN0ZWF0X2Jvb2wgPSBtZWFuKGNvbXBfanVzdGVhdF9ib29sKSwKICAgICAgICAgICAgY29tcF91YmVyX2Jvb2wgPSBtZWFuKGNvbXBfdWJlcl9ib29sKSwKICAgICAgICAgICAgY29tcF9qdXN0ZWF0X3J4X3N1cHBseSA9IHJvdW5kKG1lYW4oY29tcF9qdXN0ZWF0X3J4X3N1cHBseSkpLAogICAgICAgICAgICBjb21wX3ViZXJfcnhfc3VwcGx5ID0gcm91bmQobWVhbihjb21wX3ViZXJfcnhfc3VwcGx5KSksCiAgICAgICAgICAgICMgbnVtX3J4X2c2ID0gbWVhbihudW1fcnhfZzYpLAogICAgICAgICAgICAjIG51bV9yeF9lbnRlcnByaXNlID0gbWVhbihudW1fcnhfZW50ZXJwcmlzZSksCiAgICAgICAgICAgICMgbnVtX3J4X2xvY2FsID0gbWVhbihudW1fcnhfbG9jYWwpLAogICAgICAgICAgICBudW1fcnhfZzI1X3BjdCA9IG1lYW4obnVtX3J4X2cyNV9wY3QpLAogICAgICAgICAgICBudW1fcnhfZW50ZXJwcmlzZV9wY3QgPSBtZWFuKG51bV9yeF9lbnRlcnByaXNlX3BjdCksCiAgICAgICAgICAgICNudW1fcnhfbG9jYWxfcGN0ID0gbWVhbihudW1fcnhfbG9jYWxfcGN0KSwKICAgICAgICAgICAgI251bV9yeF9ub3NlZ21lbnRfcGN0ID0gbWVhbihudW1fcnhfbm9zZWdtZW50X3BjdCksCiAgICAgICAgICAgICMgYW9mID0gbWVhbihhb2YpLAogICAgICAgICAgICAjIGFvdiA9IG1lYW4oYW92KSwKICAgICAgICAgICAgI3BsdXNfb3JkZXJzX3BjdCA9IG1lYW4ocGx1c19vcmRlcnNfcGN0KSwKICAgICAgICAgICAgIyBjb252ZW5pZW5jZV9vcmRlcnNfcGN0ID0gbWVhbihjb252ZW5pZW5jZV9vcmRlcnNfcGN0KSwKICAgIAogICAgICAgICAgICAjIGJpY3ljbGVfZGVsaXZfcGN0ID0gbWVhbiAoYmljeWNsZV9kZWxpdl9wY3QpLAogICAgICAgICAgICAjIHNjb290ZXJfZGVsaXZfcGN0ID0gbWVhbihzY29vdGVyX2RlbGl2X3BjdCksCiAgICAgICAgICAgICMgbnVsbF9kZWxpdl9wY3QgPSBtZWFuKG51bGxfZGVsaXZfcGN0KSwKICAgICAgICAgICAgIyBudW1fYWxjb2hvbF9yeF9wY3QgICA9IG1lYW4obnVtX2FsY29ob2xfcnhfcGN0KSwKICAgICAgICAgICAgIyBudW1fYW1lcmljYW5fcnhfcGN0ID0gbWVhbihudW1fYW1lcmljYW5fcnhfcGN0KSwKICAgICAgICAgICAgIyBudW1fYnJpdGlzaF9yeF9wY3QgPSBtZWFuKG51bV9icml0aXNoX3J4X3BjdCApLAogICAgICAgICAgICAjIG51bV9jaGluZXNlX3J4X3BjdCA9IG1lYW4obnVtX2NoaW5lc2VfcnhfcGN0KSwgCiAgICAgICAgICAgICMgbnVtX2Rlc3NlcnRfcnhfcGN0ID0gbWVhbihudW1fZGVzc2VydF9yeF9wY3QpLAogICAgICAgICAgICAjIG51bV9mcmVuY2hfcnhfcGN0ID0gbWVhbihudW1fZnJlbmNoX3J4X3BjdCksCiAgICAgICAgICAgICMgbnVtX2dyZWVrX3J4X3BjdCA9IG1lYW4obnVtX2dyZWVrX3J4X3BjdCksCiAgICAgICAgICAgICMgbnVtX2luZGlhbl9yeF9wY3QgID0gbWVhbihudW1faW5kaWFuX3J4X3BjdCApLAogICAgICAgICAgICAjIG51bV9pdGFsaWFuX3J4X3BjdCA9IG1lYW4obnVtX2l0YWxpYW5fcnhfcGN0KSwKICAgICAgICAgICAgIyBudW1famFwYW5lc2VfcnhfcGN0ID0gbWVhbihudW1famFwYW5lc2VfcnhfcGN0KSwgCiAgICAgICAgICAgICMgbnVtX2tvcmVhbl9yeF9wY3QgID0gbWVhbihudW1fa29yZWFuX3J4X3BjdCApLAogICAgICAgICAgICAjIG51bV9sZWJhbmVzZV9yeF9wY3QgID0gbWVhbihudW1fbGViYW5lc2VfcnhfcGN0KSwgCiAgICAgICAgICAgICMgbnVtX21leGljYW5fcnhfcGN0ID0gbWVhbihudW1fbWV4aWNhbl9yeF9wY3QgKSwKICAgICAgICAgICAgIyBudW1fc3BhbmlzaF9yeF9wY3QgPSBtZWFuKG51bV9zcGFuaXNoX3J4X3BjdCksCiAgICAgICAgICAgICMgbnVtX3RoYWlfcnhfcGN0ID0gbWVhbihudW1fdGhhaV9yeF9wY3QpLAogICAgICAgICAgICAjIG51bV90dXJraXNoX3J4X3BjdCA9IG1lYW4obnVtX3R1cmtpc2hfcnhfcGN0KSwKICAgICAgICAgICAgIyBudW1fdmlldG5hbWVzZV9yeF9wY3QgPSBtZWFuKG51bV92aWV0bmFtZXNlX3J4X3BjdCksCiAgICAgICAgICAgICMgbnVtX21pc3NpbmdfcnhfcGN0ID0gbWVhbihudW1fbWlzc2luZ19yeF9wY3QpCiAgICAgICAgICAgICkKICAKbnVtcyA8LSB2YXBwbHkoZGZfc3VtbTIsIGlzLm51bWVyaWMsIEZVTi5WQUxVRSA9IGxvZ2ljYWwoMSkpCmRmX3N1bW0yWyxudW1zXSA8LSByb3VuZChkZl9zdW1tMlssbnVtc10sIGRpZ2l0cyA9IDIpCgp0ZXN0IDwtIGFzLmRhdGEuZnJhbWUodChkZl9zdW1tMikpCm5hbWVzKHRlc3QpIDwtIGMoJ0NsdXN0ZXIgMScsICdDbHVzdGVyIDInLCAnQ2x1c3RlciAzJywgJ0NsdXN0ZXIgNCcsICdDbHVzdGVyIDUnKQp0ZXN0IDwtIHRlc3RbLTEsXQoKdGVzdAoKCmBgYAoKPGEgbmFtZT0iYm9vdDJfYW5jaG9yIj48L2E+IAoKV2Ugbm93IGJvb3RzdHJhcCBvdXIgZGF0YXNldCB0byBzZWUgaG93IHN0YWJsZSBvdXIgY2x1c3RlcnMgcmVtYWluIG92ZXIgdGltZS4KCmBgYCB7ciBib290c3RyYXBwaW5nXzMsIG1lc3NhZ2UgPSBGLCBldmFsID0gRn0KCnN0YXJ0IDwtIFN5cy50aW1lKCkKCmNsdXN0ZXJfbGlzdCA8LSBsaXN0KCkKY2x1c3Rlcl9kZl9saXN0IDwtIGxpc3QoKQpwYWlyd2lzZV9saXN0X2Jvb3QgPC0gbGlzdCgpCgprX2hjIDwtIDUKc2FtcGxlX3NpemUgPC0gbnJvdyhkZl92YXJzMykKbl9pdGVyIDwtIDE6MjUwCm5fY2x1c3QgPC0gMTprX2hjCiAgCmZvciAoaSBpbiBuX2l0ZXIpIHsKCiAgcm93c190b19zYW1wbGUgPC0gc2FtcGxlKGMoMTpzYW1wbGVfc2l6ZSksIHJlcGxhY2UgPSBUKQogIGRmX2Jvb3QgPC0gZGZfdmFyczNbcm93c190b19zYW1wbGUsXQogIAogICNkZl9ib290IDwtIGRmX3ZhcnMyCiAgCiAgICBpZihpID09IDEpIHsKICAgICAgZGYuZGlzdC5ib290IDwtIGRmLmRpc3QKICAgICAgaGMuYm9vdCA8LSBoYwoKICAgIH0gZWxzZSB7CiAgICAgIHNldC5zZWVkKHNhbXBsZShjKDE6MTAwMCksMSkpCiAgICAgIGRmLmRpc3QuYm9vdCA8LSBkaXN0KGRmX2Jvb3RbLDI6bmNvbChkZl9ib290KV0sIG1ldGhvZCA9ICdldWNsaWRlYW4nKQogICAgICBoYy5ib290ICA8LSBoY2x1c3QoZGYuZGlzdC5ib290LCBtZXRob2QgPSAnd2FyZC5EMicpCgogIH0KICAKICBzdWJfZ3JwLmJvb3QgPC0gY3V0cmVlKGhjLmJvb3QsIGsgPSBrX2hjKQogIAogIHpvbmVfY29kZV9uYW1lcyA8LSBoYy5ib290JGxhYmVscwogIHpvbmVfY29kZV9uYW1lcyA8LSBzdWIoIi5bMS05XSsiLCAiIiwgem9uZV9jb2RlX25hbWVzKSAjdG8gcHJldmVudCByZS1uYW1pbmcgb2YgcmVwZWF0ZWQgem9uZXMKICAKICBjbHVzdGVycyA8LSBkYXRhLmZyYW1lKHpvbmVfY29kZSA9IHpvbmVfY29kZV9uYW1lcywKICAgICAgICAgICAgICAgICAgICAgICAgIGNsdXN0ZXIgPSBzdWJfZ3JwLmJvb3QsCiAgICAgICAgICAgICAgICAgICAgICAgICBpdGVyYXRpb24gPSBpKQogIGNsdXN0ZXJzJHpvbmVfY29kZSA8LSBhcy5jaGFyYWN0ZXIoY2x1c3RlcnMkem9uZV9jb2RlKQoKICAKICBwYWlyd2lzZV9saXN0X2Jvb3Quc3ViIDwtIGxpc3QoKQogIAogIGZvciAobSBpbiBuX2NsdXN0KSB7CiAgICBzdWIuY2x1c3QgPC0gY2x1c3RlcnMkem9uZV9jb2RlW2NsdXN0ZXJzJGNsdXN0ZXIgPT0gbV0KICAgIGZvciAobiBpbiAxOmxlbmd0aChzdWIuY2x1c3QpKSB7CiAgICAgIGZvciAocCBpbiAxOmxlbmd0aChzdWIuY2x1c3QpKSB7CiAgICAgIHBhaXIgPC0gcGFzdGUoc29ydChjKHN1Yi5jbHVzdFtuXSwgc3ViLmNsdXN0W3BdKSksIGNvbGxhcHNlID0gIi0iKQogICAgICBwYWlyd2lzZV9saXN0X2Jvb3Quc3ViW1twYWlyXV0gPC0gcGFpcgogICAgfQogICAgfQoKICB9CgogICAgICAjIGZvciAobSBpbiAxOm5yb3coY2x1c3RlcnMpKSB7CiAgICAgICMgICBmb3IoIG4gaW4gMTpucm93KGNsdXN0ZXJzKSkgewogICAgICAjICAgICBpZiAoY2x1c3RlcnNbbSwyXSA9PSBjbHVzdGVyc1tuLDJdKSB7CiAgICAgICMgICAgICAgcGFpciA8LSBwYXN0ZShzb3J0KGMoY2x1c3RlcnNbbSwxXSwgY2x1c3RlcnNbbiwxXSkpLCBjb2xsYXBzZSA9ICItIikKICAgICAgIyAgICAgICBwYWlyd2lzZV9saXN0X2Jvb3Quc3ViW1twYWlyXV0gPC0gcGFpcgogICAgICAjICAgICB9CiAgICAgICMgICB9CiAgICAgICMgfQogIAogIHBhaXJ3aXNlX2xpc3RfYm9vdFtbbGVuZ3RoKHBhaXJ3aXNlX2xpc3RfYm9vdCkrIDFdXSA8LSBwYWlyd2lzZV9saXN0X2Jvb3Quc3ViCiAgCiAgZm9yIChuIGluIG5fY2x1c3QpIHsKICAgIGNsdXN0ZXJfbGlzdFtbcGFzdGUwKGksICItIiwgbildXSA8LSBhcy52ZWN0b3IoY2x1c3RlcnMkem9uZV9jb2RlW2NsdXN0ZXJzJGNsdXN0ZXIgPT0gbl0pCiAgfQogIGNsdXN0ZXJfZGZfbGlzdFtbaV1dIDwtIGNsdXN0ZXJzCgogIHBhc3RlMCgiQ29tcGxldGVkOiBJdGVyYXRpb24gIixpKQp9CgplbmQgPC0gU3lzLnRpbWUoKQogIAp0aW1lLnRha2VuIDwtIGVuZCAtIHN0YXJ0ICAKICAKCmRmX2ZpbmFsX2Jvb3QgPC0gZG8uY2FsbChyYmluZCwgY2x1c3Rlcl9kZl9saXN0KQoKbnVtZXJfbGlzdCA8LSBsaXN0KCkKZGVub21fbGlzdCA8LSBsaXN0KCkKCmZvciAoaSBpbiAyOmxlbmd0aChwYWlyd2lzZV9saXN0X2Jvb3QpKSB7CiAgb3JpZyA8LSBwYWlyd2lzZV9saXN0X2Jvb3RbWzFdXQogIHRlc3QgPC0gcGFpcndpc2VfbGlzdF9ib290W1tpXV0KICBudW1lciA8LSBzdW0odGVzdCAlaW4lIG9yaWcpCiAgZGVub20gPC0gbGVuZ3RoKHRlc3QpCiAgbnVtZXJfbGlzdCBbW2xlbmd0aChudW1lcl9saXN0ICkrMV1dIDwtIG51bWVyCiAgZGVub21fbGlzdCBbW2xlbmd0aChkZW5vbV9saXN0ICkrMV1dIDwtIGRlbm9tCiAgfQoKbnVtZXJfZGYgPC0gdChkYXRhLmZyYW1lKG51bWVyX2xpc3QpKQpkZW5vbV9kZiA8LSB0KGRhdGEuZnJhbWUoZGVub21fbGlzdCkpCml0ZXJfZGYgPC0gZGF0YS5mcmFtZSh0ID0gYygxOmxlbmd0aChudW1lcl9kZikpLAogICAgICAgICAgICAgICAgICAgICAgb3JpZ19wYWlycyA9IG51bWVyX2RmLAogICAgICAgICAgICAgICAgICAgICAgdG90YWxfcGFpcnMgPSBkZW5vbV9kZikKCml0ZXJfZGYkY3VtdV9vcmlnX3BhaXJzIDwtIGN1bXN1bShpdGVyX2RmJG9yaWdfcGFpcnMpCml0ZXJfZGYkY3VtdV90b3RhbF9wYWlycyA8LSBjdW1zdW0oaXRlcl9kZiR0b3RhbF9wYWlycykKaXRlcl9kZiRtaXNtYXRjaCA8LSB3aXRoKGl0ZXJfZGYsIDEtIChjdW11X29yaWdfcGFpcnMvY3VtdV90b3RhbF9wYWlycykpCgppdGVyX2RmX2hjbHVzdCA8LSBpdGVyX2RmCgpgYGAKCgpXZSBzZWUgdGhhdCBvdXIgaGllcmFyY2hpY2FsIGNsdXN0ZXJzIGFyZSBhbHNvIGhpZ2hseSBzdGFibGUsIGFuZCBhY3R1YWxseSBtYXJnaW5hbGx5IG1vcmUgc3RhYmxlIHRoYW4gdG8gb3VyIGttZWFucyBjbHVzdGVycy4gT3ZlciB0aW1lLCBhcm91bmQgKipgciByb3VuZChpdGVyX2RmX2hjbHVzdCRtaXNtYXRjaFtucm93KGl0ZXJfZGZfaGNsdXN0KV0gKjEwMCwyKWAlKiogb2Ygb3VyIHpvbmUtcGFpcnMgZW5kIHVwIHNlZ21lbnRlZCBpbnRvIGRpZmZlcmVudCBjbHVzdGVycy4gVGhpcyBtZWFucyB0aGF0ICoqYHIgMTAwIC0gcm91bmQoaXRlcl9kZl9oY2x1c3QkbWlzbWF0Y2hbbnJvdyhpdGVyX2RmX2hjbHVzdCldICoxMDAsMilgJSoqIG9mIG91ciB6b25lLXBhaXJzIHJlbWFpbiBpbiB0aGUgc2FtZSBjbHVzdGVyLiBXaGlsZSBoaWVyYXJjaGljYWwgY2x1c3RlcmluZyBnYXZlIHVzIGhpZ2hlciBjbHVzdGVyIHN0YWJpbGl0eSwgd2UgZGVjaWRlZCB0byBnbyB3aXRoIGttZWFucyBmb3IgdGhlIGZvbGxvd2luZyByZWFzb25zOgogKiBUaGUgMiBtZXRob2RvbG9naWVzIGxlZCB0byBiYXNpY2FsbHkgdGhlIHNhbWUgY2x1c3RlcnMhIAogKiBUaGUgZGlmZmVyZW5jZSBpbiBwZXJmb3JtYW5jZSB3YXMgbWFyZ2luYWwgKDEuMzggcHApIGluIHRlcm1zIG9mIGNsdXN0ZXIgc3RhYmlsaXR5CiAqSGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcgaXMgbW9yZSBjb21wdXRhdGlvbmFsbHkgZXhwZW5zaXZlCgpgYGAge3IgYm9vdHN0cmFwcGluZ180fQpwbG90X2x5KCB4ID0gaXRlcl9kZl9oY2x1c3QkdCwgeSA9IGl0ZXJfZGZfaGNsdXN0JG1pc21hdGNoLCBtb2RlID0gJ2xpbmVzJywgdHlwZSA9ICdzY2F0dGVyJykgJT4lCiAgbGF5b3V0KHRpdGxlID0gIiUgb2YgaW5jb3JyZWN0IHBhaXJ3aXNlIG1hdGNoZXMgb3ZlciB0aW1lIiwKICAgICAgICAgeGF4aXMgPSBsaXN0KHRpdGxlID0gIk51bWJlciBvZiBJdGVyYXRpb25zIiksIAogICAgICAgICB5YXhpcyA9IGxpc3QodGl0bGUgPSAiJSBJbmNvcnJlY3QgTWF0Y2hlcyIsIHJhbmdlID0gYygwLDAuMykpKQpgYGAKCgoKCmBgYCB7ciBjb2RlX2dyYXZleWFyZH0KCiMgY2IxIDwtIGNsdXN0ZXJib290KGRmX3ZhcnMyWzI6bmNvbChkZl92YXJzMildLCBCID0gMTAwLCBib290bWV0aG9kID0gJ2Jvb3QnLCBic2NvbXBhcmUgPSBULAojICAgICAgICAgICAgICAgICAgICBtdWx0aXBsZWJvb3QgPSBULCBjbHVzdGVybWV0aG9kID0ga21lYW5zQ0JJLCBkaXNzb2x1dGlvbiA9IDAuNSwga3JhbmdlID0gNSwKIyAgICAgICAgICAgICAgICAgICAgc2VlZCA9IDE4KQojIAojIGRmX2NiIDwtIGNiMSRyZXN1bHQkcmVzdWx0CiMgZ3JvdXBzPC1jYjEkcmVzdWx0JHBhcnRpdGlvbgojIGNiMSRib290bWVhbgojIGNiMSRib290YnJkCgoKIyAKIyBpdGVyX2wgPC0gZGF0YS5mcmFtZShjbHVzdGVyX2ZpcnN0ID0gaW50ZWdlcigpLAojICAgICAgICAgICAgICAgICAgICBjbHVzdGVyX3NlY29uZCA9IGludGVnZXIoKSwKIyAgICAgICAgICAgICAgICAgICAgSi5kaXN0ID0gbnVtZXJpYygpKQojIAojIGwgPC0gMTpsZW5ndGgoY2x1c3Rlcl9saXN0KQojIAojIGZvciAoaSBpbiBuX2NsdXN0KSB7CiMgICB4IDwtIGNsdXN0ZXJfbGlzdFtbaV1dCiMgICAKIyAgIGZvcihjIGluIChsWyEobCAlaW4lIGkpXSkgKSB7CiMgICAgIHkgPC0gY2x1c3Rlcl9saXN0W1tjXV0KIyAKIyAgICAgdCA8LSBkYXRhLmZyYW1lKGNsdXN0ZXJfZmlyc3QgPSBuYW1lcyhjbHVzdGVyX2xpc3QpW1tpXV0sIAojICAgICAgICAgICAgICAgICAgICBjbHVzdGVyX3NlY29uZCA9IG5hbWVzKGNsdXN0ZXJfbGlzdClbW2NdXSwKIyAgICAgICAgICAgICAgICAgICAgSi5kaXN0ID0gbGVuZ3RoKHVuaXF1ZSh4W3ggJWluJSB5XSkpL2xlbmd0aCh1bmlxdWUoYyh4LCB5KSkpICAgICAgICkKIyAgICAgCiMgICAgICAKIyAgICAgaXRlcl9sIDwtIHJiaW5kKGl0ZXJfbCx0KQojICAgICAKIyAgIH0KIyAgIAojIH0KIyAKIyAKIyAKIyBpdGVyX2wkaXRlcl94IDwtIGFzLm51bWVyaWMoc3ViKCItLisiLCAiIiwgaXRlcl9sJGNsdXN0ZXJfZmlyc3QpKQojIGl0ZXJfbCRjbHVzX3ggPC0gYXMubnVtZXJpYyhzdWIoIi4rLSIsICIiLCBpdGVyX2wkY2x1c3Rlcl9maXJzdCkpCiMgCiMgaXRlcl9sJGl0ZXJfeTwtIGFzLm51bWVyaWMoc3ViKCItLisiLCAiIiwgaXRlcl9sJGNsdXN0ZXJfc2Vjb25kKSkKIyBpdGVyX2wkY2x1c195IDwtIGFzLm51bWVyaWMoc3ViKCIuKy0iLCAiIiwgaXRlcl9sJGNsdXN0ZXJfc2Vjb25kKSkKIyAKIyBpdGVyX3N1bW0gPC0gaXRlcl9sICU+JQojICAgZ3JvdXBfYnkoaXRlcl94LCBjbHVzX3gsIGl0ZXJfeSkgJT4lCiMgICBtdXRhdGUobWF4X0pkaXN0ID0gbWF4KEouZGlzdCkpIAojIAojIGl0ZXJfc3VtbSA8LSBpdGVyX3N1bW0gJT4lCiMgICBmaWx0ZXIoSi5kaXN0ID09IG1heF9KZGlzdCkgJT4lCiMgICBmaWx0ZXIoaXRlcl94ICE9IGl0ZXJfeSkgJT4lCiMgICBtdXRhdGUoY2x1c3Rlcl9kaXNzb2x2ZWQgPSBpZmVsc2UoSi5kaXN0IDwgMC41LCAxLCAwKSkKIyAKIyBpdGVyX3N1bW0gPC0gaXRlcl9zdW1tW29yZGVyKGl0ZXJfc3VtbSRpdGVyX3gsIGl0ZXJfc3VtbSRpdGVyX3ksIGl0ZXJfc3VtbSRjbHVzX3gpLF0KIyAgICAgICAgICAKIyBpdGVyX3N1bW0kY3VtX3N1bV9jbHVzdGVyX2Rpc3NvbHZlZCA8LSBjdW1zdW0oaXRlcl9zdW1tJGNsdXN0ZXJfZGlzc29sdmVkKQojIGl0ZXJfc3VtbSRudW0gPC0gMQojIGl0ZXJfc3VtbSR0b3RhbF9jbHVzdGVycyA8LSBjdW1zdW0oaXRlcl9zdW1tJG51bSkKIyAKIyBpdGVyX2NvbXAgPC0gaXRlcl9zdW1tW2l0ZXJfc3VtbSRjbHVzX3ggPT0gayxdICNpZSBvYnRhaW4gdGhlIGN1bXVsYXRpdmUgbnVtYmVyIG9mIGRpc3NvbHZlZCBjbHVzdGVycywgYW5kIHRvdGFsIGNsdXN0ZXJzLCBhdCB0aGUgZW5kIG9mIGVhY2ggaXRlcmF0aW9uCiMgCiMgICAKIyBpdGVyX2NvbXAkcGN0X2Rpc3NvbHZlZCA8LSBpdGVyX2NvbXAkY3VtX3N1bV9jbHVzdGVyX2Rpc3NvbHZlZC9pdGVyX2NvbXAkdG90YWxfY2x1c3RlcnMKCmBgYAoKCgoKCgoK